Should C# have a lazy key word
Asked Answered
P

3

9

Should C# have a lazy keyword to make lazy initialization easier?

E.g.

    public lazy string LazyInitializeString = GetStringFromDatabase();

instead of

    private string _backingField;

    public string LazyInitializeString
    {
        get
        {
            if (_backingField == null)
                _backingField = GetStringFromDatabase();
            return _backingField;
        }
    }
Peisch answered 2/12, 2010 at 6:7 Comment(2)
While it would be nice, you would never want to use a language that had 1000 nice features becaues nobody would ever be able to learn them all. Considering how easy it is to have the functionality (Lazy<T>, properties (like you show, though it isn't thread-safe), static initializers), it's not likely to show up in the language.Ladyship
I believe you should change your first code sample. It is incorrect not only due to a missing lazy keyword, but also because of using instance methods in field initializers.Shawanda
S
23

I don't know about a keyword but it now has a System.Lazy<T> type.

  • It is officially part of .Net Framework 4.0.
  • It allows lazy loading of a value for a member.
  • It supports a lambda expression or a method to provide a value.

Example:

public class ClassWithLazyMember
{
    Lazy<String> lazySource;
    public String LazyValue
    {
        get
        {
            if (lazySource == null)
            {
                lazySource = new Lazy<String>(GetStringFromDatabase);
                // Same as lazySource = new Lazy<String>(() => "Hello, Lazy World!");
                // or lazySource = new Lazy<String>(() => GetStringFromDatabase());
            }
            return lazySource.Value;
        }
    }

    public String GetStringFromDatabase()
    {
        return "Hello, Lazy World!";
    }
}

Test:

var obj = new ClassWithLazyMember();

MessageBox.Show(obj.LazyValue); // Calls GetStringFromDatabase()
MessageBox.Show(obj.LazyValue); // Does not call GetStringFromDatabase()

In above Test code, GetStringFromDatabase() gets called only once. I think that is exactly what you want.

Edit:

After having comments from @dthorpe and @Joe, all I can say is following is the shortest it can be:

public class ClassWithLazyMember
{
    Lazy<String> lazySource;
    public String LazyValue { get { return lazySource.Value; } }

    public ClassWithLazyMember()
    {
        lazySource = new Lazy<String>(GetStringFromDatabase);
    }

    public String GetStringFromDatabase()
    {
        return "Hello, Lazy World!";
    }
}

Because following does not compile:

public Lazy<String> LazyInitializeString = new Lazy<String>(() =>
{
    return GetStringFromDatabase();
});

And that property is type of Lazy<String> not String. You you always need to access it's value using LazyInitializeString.Value.

And, I am open for suggestions on how to make it shorter.

Steve answered 2/12, 2010 at 6:8 Comment(6)
My mind is still intact, but I am sufficiently impressed to upvote.Osrock
Isn't this exactly the same as what the original poster proposed? You've got exactly the same steps in your code as in his - define a backing store, implement the getter to allocate on first use, and return the cached value on subsequent gets. This code doesn't seem to be taking advantage of any laziness provided by the Lazy<T> type since the value is taken immediately after it is constructed.Argufy
@Argufy is right - the sample would be more realistic if you assign lazySource when it is declared, and remove the test for null from the property getter.Harry
@decyclone, I do not think you should bother about your last sample because of it doesn't compile. It doesn't compile because of GetStringFromDatabase is not static, but in the same way OP's original syntax will not be compilable at the moment (even if we skip new lazy keyword), because of the same reason - method is not static.Shawanda
@Snowbear: I know why the last code snippet does not compile. I provided the shortest possible solution as per my knowledge that does compile and works.Steve
@decyclone, I believe you know why, I just wanted to tell that shortest non-compilable is also fine ;)Shawanda
C
12

Have you considered using System.Lazy<T>?

public Lazy<String> LazyInitializeString = new Lazy<String>(() =>
{
    return GetStringFromDatabase();
});

(This does have the disadvantage that you need to use LazyInitializeString.Value instead of just LazyInitializeString.)

Coauthor answered 2/12, 2010 at 6:8 Comment(6)
And worth to mention that it's in C#4.0Daffi
In this example, I think new Lazy(GetStringFromDatabase); should work - no extra lambda, and inferred generic type. C# is awesome.Theaterintheround
@Jani: It's not part of C# 4. It's part of .NET 4. It's worth differentiating between language and framework version.Winterwinterbottom
@Jon, @Jani - especially since it's possible to write your own versions of stuff in a future framework if you need to (I wouldn't do without my 2.0 HashSet in a 2.0 project I maintain) but you can't write your own language features to the same extent.Digitize
I do know about Lazy<T> but as you say it requires using .Value and also limits it to a read-only property.Peisch
@Jonathan Parker, well the other advantage of being able to write your own copy, is you can write a different version. My 2.0 HashSet isn't the same as the 3.5 one, because I wanted extra functionality. Likewise, the SettableLazy in my new answer does what you way here you want.Digitize
D
5

Okay, you say in a comment that Lazy<T> won't suffice for you because it's readonly and you have to call .Value on it.

Still, it's clear that we want something along those lines - we already have a syntax for describing an action that is to be called, but not called immediately (indeed we have three; lambda, delegate creation and bare method name as a shortcut to the latter - the last thing we need is a fourth).

But we can quickly put together something that does that.

public enum SettableLazyThreadSafetyMode // a copy of LazyThreadSafetyMode - just use that if you only care for .NET4.0
{
    None,
    PublicationOnly,
    ExecutionAndPublication
}
public class SettableLazy<T>
{
    private T _value;
    private volatile bool _isCreated;
    private readonly Func<T> _factory;
    private readonly object _lock;
    private readonly SettableLazyThreadSafetyMode _mode;
    public SettableLazy(T value, Func<T> factory, SettableLazyThreadSafetyMode mode)
    {
        if(null == factory)
            throw new ArgumentNullException("factory");
        if(!Enum.IsDefined(typeof(SettableLazyThreadSafetyMode), mode))
           throw new ArgumentOutOfRangeException("mode");
        _lock = (_mode = mode) == SettableLazyThreadSafetyMode.None ? null : new object();
        _value = value;
        _factory = factory;
        _isCreated = true;
    }
    public SettableLazy(Func<T> factory, SettableLazyThreadSafetyMode mode)
        :this(default(T), factory, mode)
    {
        _isCreated = false;
    }
    public SettableLazy(T value, SettableLazyThreadSafetyMode mode)
        :this(value, () => Activator.CreateInstance<T>(), mode){}
    public T Value
    {
        get
        {
            if(!_isCreated)
                switch(_mode)
                {
                    case SettableLazyThreadSafetyMode.None:
                        _value = _factory.Invoke();
                        _isCreated = true;
                        break;
                    case SettableLazyThreadSafetyMode.PublicationOnly:
                        T value = _factory.Invoke();
                        if(!_isCreated)
                            lock(_lock)
                                if(!_isCreated)
                                {
                                    _value = value;
                                    Thread.MemoryBarrier(); // ensure all writes involved in setting _value are flushed.
                                    _isCreated = true;
                                }
                        break;
                    case SettableLazyThreadSafetyMode.ExecutionAndPublication:
                        lock(_lock)
                        {
                            if(!_isCreated)
                            {
                                _value = _factory.Invoke();
                                Thread.MemoryBarrier();
                                _isCreated = true;
                            }
                        }
                        break;
                }
            return _value;
        }
        set
        {
            if(_mode == SettableLazyThreadSafetyMode.None)
            {
                _value = value;
                _isCreated = true;
            }
            else
                lock(_lock)
                {
                    _value = value;
                    Thread.MemoryBarrier();
                    _isCreated = true;
                }
        }
    }
    public void Reset()
    {
        if(_mode == SettableLazyThreadSafetyMode.None)
        {
            _value = default(T); // not strictly needed, but has impact if T is, or contains, large reference type and we really want GC to collect.
            _isCreated = false;
        }
        else
            lock(_lock) //likewise, we could skip all this and just do _isCreated = false, but memory pressure could be high in some cases
            {
                _value = default(T);
                Thread.MemoryBarrier();
                _isCreated = false;
            }
    }
    public override string ToString()
    {
        return Value.ToString();
    }
    public static implicit operator T(SettableLazy<T> lazy)
    {
        return lazy.Value;
    }
    public static implicit operator SettableLazy<T>(T value)
    {
        return new SettableLazy<T>(value, SettableLazyThreadSafetyMode.ExecutionAndPublication);
    }
}

Adding some more constructor overloads is left as an exercise to the reader :)

This will more than suffice:

private SettableLazy<string> _backingLazy = new SettableLazy<string>(GetStringFromDatabase);

public string LazyInitializeString
{
    get
    {
        return _backingLazy;
    }
    set
    {
        _backingLazy = value;
    }
}

Personally, I'm not overjoyed at the implicit operators, but they do show your requirements can be met. Certainly no need for another language feature.

Digitize answered 2/12, 2010 at 16:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.