Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# 4 Lazy Loading & Lazy<T>

I have a cart model class that has a List property like so:

public List<CartItem> CartItems
{
    get
    {
        if (_cartItems == null)
            _cartItems = Services.CartItemService.GetCartItems();

        return _cartItems;
    }
}
private List<CartItem> _cartItems;

This is fine, unless the service that's used to query the data from SQL Server returns null, in which case, the database can be unnecessarily hit multiple times as CartItems is referenced. I then noticed that Lazy<T> was available to me, so I attempted to modify my code slightly (since Lazy<T> accounts for null and would prevent multiple hits to the database)

public List<CartItem> CartItems
{
    get
    {
        return _cartItems.Value;
    }
}

private Lazy<List<CartItem>> _cartItems = new Lazy<List<CartItem>>(() =>
{
    // return Services.CartItemService.GetCartItems(); cannot be called here :(
});

The compile time error is

"A field initializer cannot reference the non-static field, method, or property"

Services is a public property in the same class as CartItems but I can't figure out if it's even possible to access that in a Func<List<CartItem>> delegate. I do not want to have to create factory classes for each property - I have to us something like this in many places, and I want to be ... well.... lazy.

like image 934
Chris Klepeis Avatar asked Nov 28 '22 04:11

Chris Klepeis


1 Answers

To answer your question in a comment:

I'm curious now as to why it works in the constructor and not in my example.

The order of construction of a C# object goes like this. First all the field initializers are executed, in order from most to least derived class. So if you have a class B, and a derived class D, and you create a new D, then the field initializers of D all run before any field initializer of B.

Once all the field initializers have run then the constructors run in order from least derived to most derived. That is, first the constructor body of B runs, then the constructor body of D runs.

This might seem odd, but the reasoning is straightforward:

  • First off, clearly the base class ctor bodies must run before the derived class ctor bodies. The derived ctors might depend on state initialized by the base class ctor, but the opposite is unlikely to be true; the base class typically does not know about the derived class.

  • Second, clearly it is expected that initialized fields have their values before the constructor bodies run. It would be very strange for a constructor to observe an uninitialized field when there is a field initializer right there.

  • Therefore, the way we code generate ctors is that every ctor follows the pattern: "Initialize my fields, then call my base class ctor, then execute my ctor". Since everyone follows that pattern, all the fields are initialized in order from derived to base, and all the ctor bodies are run from base to derived.

OK, so now that we've established that, what happens when a field initializer references "this" either explicitly or implicitly? Why would you do that? "this" is probably going to be used to access a method, field, property or event on an object whose field initializers have not all run, and none of the constructor bodies have run! Clearly that is incredibly dangerous. Most of the state required for correct operation of the class is still missing.

Therefore we disallow any reference to "this" in any field initializer.

By the time you get to a particular constructor body, you know that all the field initializers have run and that all the constructors for all your base classes have run. You have much more evidence that the object is likely to be in a good state, so "this" access is allowed in a constructor body. (You can still do foolishly dangerous things; for example, you could call a virtual method overridden in a derived class when inside a base class constructor; the derived constructor has not run yet and the derived method might fail as a result. Be careful!)

Now you might quite reasonably say that there is a big difference between:

class D : B
{
    int x = this.Whatever(); // call a method on the base class, whose ctor has not run!

and

class D : B
{
    Func<int> f = this.Whatever;

or similarly:

class D : B
{
    Func<int> f = ()=>this.Whatever();

That doesn't call anything. It doesn't read any state that might be uninitialized. Clearly this is perfectly safe. We could make a rule that says "Allow this access in a field initializer when the access is in a method-group-to-delegate conversion or a lambda", right?

Nope. That rule allows for an end-run around the safety system:

class D : B
{
    int x = ((Func<int>)this.Whatever)();

And we're back in the same boat again. So we could make a rule that says "allow this access in a field intializer when the access is in a method-group-to-delegate-conversion or a lambda and a flow analyzer can prove that the delegate is not invoked before the constructor runs" and hey, now the language design team and the compiler implementation, development, testing and user education teams are spending an enormous amount of time, money and effort on solving a problem that we don't want to solve in the first place.

It is better for the language to have simple, understandable rules that promote safety and can be correctly implemented, easily tested and clearly documented than to have complex rules that allow for obscure scenarios to work. The simple, safe rule is an instance field initializer cannot have any explicit or implicit reference to 'this', period.

Further reading:

http://blogs.msdn.com/b/ericlippert/archive/2008/02/15/why-do-initializers-run-in-the-opposite-order-as-constructors-part-one.aspx

http://blogs.msdn.com/b/ericlippert/archive/2008/02/18/why-do-initializers-run-in-the-opposite-order-as-constructors-part-two.aspx

like image 119
Eric Lippert Avatar answered Dec 05 '22 09:12

Eric Lippert