为什么委托不能在容器中添加带有引用的侦听器

why delegate cannot add a listener with a reference in container

我创建了一个 字典并将委托对象添加到容器中。 并尝试将另一个侦听器方法添加到该委托中,该委托通过 Dicionary 的 TryGetValue 方法获取引用。 结果显示我们无法输出事件处理器2,为什么?

class Program
{

    public delegate void TestDelegate();
    static void Main(string[] args)
    {
        Dictionary<int, TestDelegate> delegates = new Dictionary<int, TestDelegate>();
        delegates.Add(1, () =>
        {
            Console.WriteLine("event handler 1");
        });
        if (delegates.TryGetValue(1, out TestDelegate td))
        {
            Console.WriteLine($"delegates same Reference {ReferenceEquals(td, delegates[1])}");
            td += () => Console.WriteLine("event handler 2");
        }
        delegates[1] += () => Console.WriteLine("event handler 3");
        if (delegates.TryGetValue(1, out TestDelegate td2))
        {
            td2.Invoke();
        }
    }
}

委托是不可变的对象。 + 运算符 returns 一个新的委托实例,其调用列表包含添加在一起的两个委托。

你做到了:

delegates.TryGetValue(1, out TestDelegate td);
td += () => Console.WriteLine("event handler 2");

将对委托的引用从字典复制到 tdtd += ... 然后将 td 的调用列表与您指定的新委托组合到一个新的委托实例中,并将对该新委托实例的引用存储在 td.

因此,我们更新了 td 以指向一个新的委托实例,但我们还没有更新 delegates[1] 中保存的引用。当我们稍后访问 delegates[1] 以调用它时,该实例的调用列表将不包含 () => Console.WriteLine("event handler 2").


strings 是相似的:它们是对不可变对象的引用。考虑:

string s1 = "Hello";
string s2 = s1;
s2 += ", World";
Console.WriteLine(s1);

打印“Hello”:+ 创建了一个新的字符串实例并将其分配给 s2,但我们没有更新 s1 以引用该新字符串实例,并且我们没有改变 s1 所指的字符串。

td += () => Console.WriteLine("event handler 2");可以展开为:

td = td + () => Console.WriteLine("event handler 2");

所以你可以看到你只是重新分配给一个局部变量,而不是更新原来的委托实例,顺便说一下,它是不可变的。


另一方面,当您这样做时:

delegates[1] += () => Console.WriteLine("event handler 3");

扩展为:

delegates[1] = delegates[1] + () => Console.WriteLine("event handler 3");

所以你委托替换字典中的值,这是原始处理程序和新处理程序的组合.