EmptyEnumerable <T> .Instance赋值和多线程设计

syn*_*hko 4 .net c# multithreading .net-4.5

这更像是一个设计问题,而不是实际的错误或咆哮.我想知道人们对以下行为的看法:

在.NET中,当您想要有效地表示空IEnumerable时Enumerable.Empty<MyType>(),这将缓存空的可枚举实例.这是一个很好的免费微优化我认为如果依赖很多可能会有所帮助.

但是,这是实现的样子:

public static IEnumerable<TResult> Empty<TResult>() {
    return EmptyEnumerable<TResult>.Instance;
}


internal class EmptyEnumerable<TElement>
{
    static volatile TElement[] instance;

    public static IEnumerable<TElement> Instance {
        get {
            if (instance == null) instance = new TElement[0];
            return instance;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望在一次锁定之后,在另一次空检查之后发生赋值,但事实并非如此.

我想知道这是否是一个有意识的决定(即我们不关心可能创建几个对象,如果同时访问它们,我们将立即丢弃,因为我们宁愿避免锁定)或只是无知?

你会怎么做?

usr*_*usr 5

这是安全的,因为volatile序列对该字段的所有读取和写入.在读入之前,return instance;总是至少有一个写入设置该字段为有效值.

目前还不清楚将返回什么值,因为这里可能会创建多个数组.但总会有一个非空数组.

他们为什么这样做?好吧,锁具有更多的开销,volatile并且实现很容易实现.如果多个线程碰巧与此方法竞争,那么这些额外的实例将仅创建几次.对于每个赛程最多,将创建一个实例.初始化完成后,没有垃圾.


注意,没有volatile,实例字段可以在分配后翻转回零.这非常直观.没有任何同步,编译器可以像这样重写代码:

var instanceRead1 = instance;
var returnValue;
if (instanceRead1 == null) {
    returnValue = new TElement[0];
    instance = returnValue;
}

var instanceRead2 = instance;
if (instanceRead2 == returnValue) return instanceRead2;
else return null;
Run Code Online (Sandbox Code Playgroud)

在存在并发写入时,instanceRead2可以使用与刚写入的值不同的值.没有编译器会进行这样的重写,但它是合法的.CPU 可能会在某些体系结构上执行类似的操作.不太可能,但合法.也许有一个更合理的重写.