C#应该有一个懒惰的关键词

Jon*_*ker 9 .net c# lazy-initialization

C#应该有一个惰性关键字来使延迟初始化更容易吗?

例如

    public lazy string LazyInitializeString = GetStringFromDatabase();
Run Code Online (Sandbox Code Playgroud)

代替

    private string _backingField;

    public string LazyInitializeString
    {
        get
        {
            if (_backingField == null)
                _backingField = GetStringFromDatabase();
            return _backingField;
        }
    }
Run Code Online (Sandbox Code Playgroud)

dec*_*one 23

我不知道关键字,但它现在有一个System.Lazy<T>类型.

  • 它是.Net Framework 4.0的正式组成部分.
  • 它允许延迟加载a的值member.
  • 它支持a lambda expression或a method提供值.

例:

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!";
    }
}
Run Code Online (Sandbox Code Playgroud)

测试:

var obj = new ClassWithLazyMember();

MessageBox.Show(obj.LazyValue); // Calls GetStringFromDatabase()
MessageBox.Show(obj.LazyValue); // Does not call GetStringFromDatabase()
Run Code Online (Sandbox Code Playgroud)

在上面的测试代码中,GetStringFromDatabase()只调用一次.我认为这正是你想要的.

编辑:

在得到@dthorpe和@Joe的评论后,我所能说的就是它可以是最短的:

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!";
    }
}
Run Code Online (Sandbox Code Playgroud)

因为以下不编译:

public Lazy<String> LazyInitializeString = new Lazy<String>(() =>
{
    return GetStringFromDatabase();
});
Run Code Online (Sandbox Code Playgroud)

那个属性Lazy<String>不是String.你总是需要使用它来获取它的价值LazyInitializeString.Value.

并且,我对如何缩短它的建议持开放态度.

  • 我的思绪仍然完好无损,但我对upvote印象深刻. (4认同)
  • 这与原始海报提出的不完全相同吗?您在代码中的步骤与在他的代码中完全相同 - 定义后备存储,实现首次使用时分配的getter,并在后续获取时返回缓存值.这段代码似乎没有利用Lazy <T>类型提供的任何惰性,因为值是在构造后立即获取的. (2认同)

Jam*_*lis 12

你考虑过用过System.Lazy<T>吗?

public Lazy<String> LazyInitializeString = new Lazy<String>(() =>
{
    return GetStringFromDatabase();
});
Run Code Online (Sandbox Code Playgroud)

(这确实有缺点,你需要使用LazyInitializeString.Value而不是仅仅LazyInitializeString.)

  • @Jani:它不是C#4的一部分.它是.NET 4的一部分.值得区分语言和框架版本. (7认同)
  • 值得一提的是它在C#4.0中 (3认同)

Jon*_*nna 5

好吧,你在评论中说,这Lazy<T>对你来说是不够的,因为它是只读的,你必须要求.Value它.

尽管如此,很明显我们想要一些东西 - 我们已经有了一个语法来描述一个要调用的动作,但是没有立即调用(实际上我们有三个; lambda,委托创建和裸方法名称作为后者 - 我们需要的最后一件事是第四件事.

但我们可以快速整理出那样做的东西.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

添加一些更多的构造函数重载留给读者:)

这样就足够了:

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

public string LazyInitializeString
{
    get
    {
        return _backingLazy;
    }
    set
    {
        _backingLazy = value;
    }
}
Run Code Online (Sandbox Code Playgroud)

就个人而言,我并不喜欢隐含的操作员,但他们确实表明你的要求可以得到满足.当然不需要其他语言功能.