为啥通过for循环插入时List中的代表总是相等但没有循环时不相等

Posted

技术标签:

【中文标题】为啥通过for循环插入时List中的代表总是相等但没有循环时不相等【英文标题】:Why delegates inside List are always equal when inserted through for-loop but not equal without loop为什么通过for循环插入时List中的代表总是相等但没有循环时不相等 【发布时间】:2022-01-07 11:23:02 【问题描述】:

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

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");
        
    

输出:

两个代表不匹配

现在,如果我们修改代码并使用 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");
        
    

输出:

两个代表相等

我想,我的问题不需要再解释了——为什么使用for循环后会产生差异?

【问题讨论】:

我认为这实际上可能是实现定义的; Eric Lippert 在this article 中给出了一个类似的例子,用于他列表中的“第四个因素”。 进一步的问题:无论如何评估代表的平等性?引用?如果是,那么循环中第一个委托的调用不会触发第二个委托吗? 【参考方案1】:

请注意,language specification 允许这里的两个 lambda 表达式都转换为同一个委托实例:

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

允许(但不要求)将具有相同(可能为空)捕获的外部变量实例集的语义相同的匿名函数转换为相同的委托类型,以返回相同的委托实例。

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

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

for (int i = 0; i < 2; i++)

    int x = 0;
    actionListenerList.Add(
        (message) => 
            Console.WriteLine(message);
            _ = x;
        
    );

【讨论】:

您的解释是有道理的,但我已经尝试过使用new 表达式的示例,但它不起作用。你能让它工作吗? 似乎更多地取决于上下文。循环内的局部变量捕获导致 2 个不同的实例:fiddle @Sweeper “我猜 Jon Skeet 的方式是唯一的方式。”我想你可以把这个添加到facts list 有趣,我不知道规范允许组合匿名函数【参考方案2】:
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);

【讨论】:

那么,是否可以使用 for 循环创建两个不同的委托实例? 除非你在循环之外声明它。你为什么会关心,这是个问题 如果你在循环范围内引入一个局部变量,然后在 lambda 表达式中捕获它,那将创建多个实例...... @JonSkeet 非常感谢。您的方法非常有效。但是,这是如何工作的?你能解释一下吗? @UmFraWJ1bCBJc2xhbQ:我现在没有时间详细解释,但从根本上说,如果有不同的上下文,代表 必须 会有所不同(因为他们可以产生不同的结果)。

以上是关于为啥通过for循环插入时List中的代表总是相等但没有循环时不相等的主要内容,如果未能解决你的问题,请参考以下文章

为啥不总是在 vue.js for 循环中使用索引作为键?

普通for循环遍历List时调用remove方法,List没有遍历完。为啥?

为啥通过jQuery中的for循环后数组顺序是随机的? [复制]

为啥 If 语句不适用于 selenium 中的 for 循环

为啥 Spring 在配置时并不总是使用批量插入/更新?

vue中v-for循环的时候为啥要添加:key属性