为什么 List 中的委托在通过 for 循环插入时总是相等,但在没有循环的情况下不相等

Why delegates inside List are always equal when inserted through for-loop but not equal without loop

让我们看下面的代码示例。有一个委托列表,随后两个实例按顺序插入到该列表中。

class Client
{
    delegate void ActionListener(string message);
        
    public static void Main()
    {
        List<ActionListener> actionListenerList = new List<ActionListener>();

        actionListenerList.Add((message) => Console.WriteLine(message));
        actionListenerList.Add((message) => Console.WriteLine(message));

        if (actionListenerList[0].Equals(actionListenerList[1]))
        {
            Console.WriteLine("Two Delegates are equal");
        }
        else
        {
            Console.WriteLine("Two Delegates did not match");
        }
    }
}

Output:

Two Delegates did not match

现在,如果我们修改代码并使用 for 循环将委托添加到列表中,那么输出将完全不同。

class Client
{
    delegate void ActionListener(string message);
        
    public static void Main()
    {
        List<ActionListener> actionListenerList = new List<ActionListener>();

        for (int i = 0; i < 2; i++)
        {
            actionListenerList.Add((message) => Console.WriteLine(message));
        }

        if (actionListenerList[0].Equals(actionListenerList[1]))
        {
            Console.WriteLine("Two Delegates are equal");
        }
        else
        {
            Console.WriteLine("Two Delegates did not match");
        }
    }
}

Output:

Two Delegates are equal

我想,我的问题不需要进一步解释-为什么使用for循环后会产生差异?

actionListenerList.Add((message) => Console.WriteLine(message));
actionListenerList.Add((message) => Console.WriteLine(message));

在这个版本中,有两个不同的功能。 lambda 只是一个单行函数,所以如果你有两个,那么它们中的每一个的委托将不相等。

实际上就好像你写了:

actionListenerList.Add(Lambda1);
actionListenerList.Add(Lambda2);

void Lambda1(string message)
{
    Console.WriteLine(message);
}

void Lambda2(string message)
{
    Console.WriteLine(message);
}

for (int i = 0; i < 2; i++)
{
    actionListenerList.Add((message) => Console.WriteLine(message));
}

在这个版本中,只有一个函数,所以每个委托都指向同一个函数。

就好像你写的一样

for (int i = 0; i < 2; i++)
{
    actionListenerList.Add(Lambda1);
}

void Lambda1(string message)
{
    Console.WriteLine(message);
}

请注意,language specification 允许 此处的两个 lambda 表达式转换为相同的委托实例:

actionListenerList.Add((message) => Console.WriteLine(message));
actionListenerList.Add((message) => Console.WriteLine(message));

Conversions of semantically identical anonymous functions with the same (possibly empty) set of captured outer variable instances to the same delegate types are permitted (but not required) to return the same delegate instance.

因此您可能会看到在您的编译器上创建了两个不同的委托实例,但在另一个编译器上,lambda 可以转换为相同的委托实例,作为优化。

为了确保您绝对拥有不同的委托实例(尽管我认为这很少有用),您可以在循环内捕获一个局部变量 ()。现在 lambda 没有“同一组捕获的局部变量”:

for (int i = 0; i < 2; i++)
{
    int x = 0;
    actionListenerList.Add(
        (message) => {
            Console.WriteLine(message);
            _ = x;
        }
    );
}