15

问候我今天做了一些懒惰的初始化代码,并想为什么不使用 null-coalescing 运算符来执行此操作,它更短,但后来我想这样做是否有任何开销或额外成本。

下面是简化的示例代码,显示了用于延迟初始化的更常见的形式,然后是使用空合并运算符的形式。它们具有完全相同的结果,并且看起来相同。我的第一个想法是,在创建对象之后,现在使用??. 这是否不是问题,编译器/JIT 会以某种方式对其进行优化,是否有更邪恶的事情发生,您永远不应该使用 进行延迟初始化??,或者它是完全安全的,并且不会产生任何不好的 mojo。

private MyLazyObject _lazyObject;

public MyLazyObject GetMyLazyObjectUsingMoreCommonMethod()
{
    if (_lazyObject != null)
        return _lazyObject;

    _lazyObject = new MyLazyObject();

    return _lazyObject;
}

public MyLazyObject GetMyLazyObjectUsingNullCoalescingOpMethod()
{
    _lazyObject = _lazyObject ?? new MyLazyObject();
    return _lazyObject;
}
4

4 回答 4

16

是的,一个叫做线程安全的小东西。您提供的两种方法在功能上是等效的,因此 null 合并运算符本身还不错,但是您列出的两种方法都不是线程安全的,因此如果两个线程尝试同时调用您的Get方法,你最终可能会产生两个MyLazyObjects。这可能没什么大不了的,但这可能不是您所希望的。

如果您使用的是 .NET 4,只需使用Lazy.

private Lazy<MyLazyObject> _lazyObject = 
    new Lazy<MyLazyObject>(() => new MyLazyObject());

public MyLazyObject MyLazyObject {get {return _lazyObject.Value;}}

代码简洁,易于理解,线程安全。

于 2011-09-13T21:34:13.007 回答
5

它是完全安全且定义明确的——事实上,这意味着编译器可以只复制堆栈的头部(dup)并存储一次,而不是存储字段、加载字段。

唯一出现问题的是不存在的 c# 1.2 (.NET 1.1)。

于 2011-09-13T21:37:45.260 回答
2

语法糖中的空值合并运算符。本质上它与您的第一个示例相同,我不相信 JIT 编译器会对其进行特殊优化。您应该更关心的是方法的线程安全性。空合并运算符不是原子的,这意味着您应该确保MyLazyObject在返回之前以线程安全的方式实例化您的。

于 2011-09-13T21:36:12.933 回答
2

您甚至可以更改第一种方法的代码:

public MyLazyObject GetMyLazyObjectUsingMoreCommonMethod()
{
    if (_lazyObject == null)
        _lazyObject = new MyLazyObject();

    return _lazyObject;
}

这将提供与

public MyLazyObject GetMyLazyObjectUsingNullCoalescingOpMethod()
{
    _lazyObject = _lazyObject ?? new MyLazyObject();

    return _lazyObject;
}

如前所述,它只是语法糖。

于 2011-09-13T21:43:52.967 回答