【问题标题】:Should C# have a lazy key wordC#是否应该有一个惰性关键字
【发布时间】:2010-12-02 06:07:08
【问题描述】:

C# 是否应该有一个lazy 关键字以使延迟初始化更容易?

例如

    public lazy string LazyInitializeString = GetStringFromDatabase();

而不是

    private string _backingField;

    public string LazyInitializeString
    {
        get
        {
            if (_backingField == null)
                _backingField = GetStringFromDatabase();
            return _backingField;
        }
    }

【问题讨论】:

  • 虽然它会很好,但您永远不会想要使用具有 1000 个不错功能的语言,因为没有人能够学习所有这些功能。考虑到拥有功能(Lazy<T>、属性(如您展示的,虽然它不是线程安全的)、静态初始化器)是多么容易,它不太可能出现在语言中。
  • 我相信您应该更改您的第一个代码示例。它不正确不仅是因为缺少 lazy 关键字,还因为在字段初始值设定项中使用了实例方法。

标签: c# .net lazy-initialization


【解决方案1】:

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

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

示例:

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!";
    }
}

测试:

var obj = new ClassWithLazyMember();

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

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

编辑:

在获得@dthorpe 和@Joe 的cmets 之后,我只能说以下是最短的:

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!";
    }
}

因为以下无法编译:

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

并且该属性的类型是 Lazy&lt;String&gt; 而不是 String。你总是需要使用LazyInitializeString.Value 来访问它的价值。

而且,我愿意接受有关如何缩短它的建议。

【讨论】:

  • 我的思想仍然完好无损,但我印象深刻,可以投票。
  • 这不是和原发帖人提议的完全一样吗?您的代码中的步骤与他的完全相同 - 定义一个后备存储,实现 getter 以在首次使用时分配,并在后续获取时返回缓存值。这段代码似乎没有利用 Lazy 类型提供的任何惰性,因为该值是在构造后立即获取的。
  • @dthorpe 是对的 - 如果您在声明lazySource 时分配该示例,并从属性 getter 中删除对 null 的测试,则该示例会更真实。
  • @decyclone,我认为你不应该因为it doesn't compile而为你的最后一个样本而烦恼。它无法编译,因为GetStringFromDatabase 不是静态的,但同样,OP 的原始语法目前无法编译(即使我们跳过新的lazy 关键字),因为同样的原因 - 方法不是静态的。
  • @Snowbear:我知道为什么最后的代码 sn-p 无法编译。据我所知,我提供了可以编译和工作的最短的解决方案。
【解决方案2】:

您是否考虑过使用System.Lazy&lt;T&gt;

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

(这样做的缺点是您需要使用LazyInitializeString.Value 而不仅仅是LazyInitializeString。)

【讨论】:

  • 值得一提的是它在 C#4.0 中
  • 在这个例子中,我认为new Lazy(GetStringFromDatabase); 应该可以工作 - 没有额外的 lambda 和推断的泛型类型。 C# 很棒。
  • @Jani:它不是 C# 4 的一部分。它是 .NET 4 的一部分。值得区分语言和框架版本。
  • @Jon, @Jani - 特别是如果你需要的话,可以在未来的框架中编写你自己版本的东西(我不会在我维护的 2.0 项目中没有我的 2.0 HashSet)但你不能在同样程度上编写自己的语言特性。
  • 我确实知道 Lazy 但正如您所说,它需要使用 .Value 并将其限制为只读属性。
【解决方案3】:

好的,你在评论中说 Lazy&lt;T&gt; 对你来说不够用,因为它是只读的,你必须调用 .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);
    }
}

添加更多的构造函数重载留给读者作为练习:)

这已经足够了:

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

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

就我个人而言,我对隐式运算符并不感到高兴,但它们确实表明您的要求可以得到满足。当然不需要其他语言功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多