为什么在 C# 中将委托分配给本地副本线程安全,任何代码都可以证明这一点?

Why assigning a delegate to a local copy thread safe in C#, any code can prove it?

我听说 C# 中的多播委托是 immutable.But 下面的代码似乎证明它不是。

Action a = () => { };
Action b = a;
Console.WriteLine(ReferenceEquals(a, b));

它将显示 True 而不是 False。这是另一个例子:

public delegate void MyHandler();
public class Klass
{
    public event MyHandler onCreate;

    public void Create()
    {
        MyHandler handler = onCreate;
        if (handler != null)
        {
            handler();
        }

        Console.WriteLine(ReferenceEquals(handler, onCreate));
    }
}

此 class 将按如下方式调用:

Klass k = new Klass();
k.onCreate += () => { };

k.Create();

和上面一样会显示True,那么如果本地副本与原始副本是相同的引用,那怎么线程安全?

How could it be thread safe if the local copy is the same reference as the orignal one?

我想你可能误解了不变性的含义。维基百科是这样定义它的:

In object-oriented and functional programming, an immutable object is an object whose state cannot be modified after it is created. This is in contrast to a mutable object, which can be modified after it is created.

从变量 ab 的引用赋值不会更改委托的状态。因此,调用同一个委托的多个线程保证调用同一个构造的委托。请注意,这并不意味着该委托引用的底层方法是线程安全的。请记住,C# 中的委托是引用类型,它们是按值复制的,在它们的例子中是 GC 堆中持有的对象的地址,而不是值本身的副本。 Are C# delegates thread-safe?

中有关委托线程安全的更多信息

有两个概念使该模式成为线程安全的。

  • 委托是不可变的。一旦创建,就永远不会改变。这意味着您可以在代码中使用它,而不必担心另一个线程会更改它。
  • 引用分配是原子的。复制对局部变量的引用可防止清除多播链的一个线程与当前线程之间的竞争条件假设它将引用 null.
  • 以外的其他内容

这里一个可能的混淆是事件的工作方式类似于变量,因为它持有对多播委托的引用。当您对其执行 +=-= 时,它将构造一个新的多播委托并将新创建的对象分配给事件 "variable"。委托仍然是不可变的,所以它永远不会改变。它是不可以由另一个线程更改的事件。