技术问询:从单个本地函数创建的多个委托是否保证相等?
关于本地函数创建多个委托的等价性问题
首先直接给你核心结论:在单次方法调用内,从同一个本地函数创建的多个委托是完全相等的,不需要刻意只创建一个实例。下面分情况给你掰扯清楚:
1. 没捕获外部变量的本地函数
如果本地函数没用到方法里的外部变量,编译器会把它编译成静态方法(或者和包含它的方法同实例的非静态方法,取决于包含方法是不是静态的)。这时候每次创建的委托:
Target属性要么是null(本地函数是静态的情况),要么是包含方法的实例(非静态情况)Method属性指向的是同一个编译后的方法
在.NET里判断委托相等的规则很简单:只要两个委托的Target和Method都相同,就判定为相等。所以这种情况下,多次创建的委托肯定是等价的。
举个代码例子验证:
void TestNoCapture() { void LocalFunc() => Console.WriteLine("没捕获变量的本地函数"); Action d1 = LocalFunc; Action d2 = LocalFunc; Console.WriteLine(d1 == d2); // 输出 True }
2. 捕获了外部变量的本地函数
要是本地函数用到了方法里的变量(比如某个局部变量),编译器会悄悄生成一个闭包类——把捕获的变量当成这个类的字段,本地函数则变成这个类的实例方法。在单次方法调用里,这个闭包类的实例只会创建一次,所有基于这个本地函数创建的委托,都会共享同一个闭包实例作为Target,同时Method指向的也是同一个闭包类的方法。
所以这种情况下,多次创建的委托依然满足Target和Method都相同的条件,自然也是相等的。
看这个例子:
void TestWithCapture() { int counter = 0; void LocalFunc() => counter++; Action d1 = LocalFunc; Action d2 = LocalFunc; Console.WriteLine(d1 == d2); // 输出 True d1.Invoke(); Console.WriteLine(counter); // 输出 1(因为两个委托共享同一个闭包里的counter) }
关于你提到的事件示例:为啥会打印多行?
你说的那种多次订阅同一个本地函数委托到事件的情况,哪怕委托相等,事件触发时还是会执行多次。这是因为事件的调用列表允许存重复的委托条目——每次调用+=都会把委托加进去,不管它已经在不在列表里。
比如下面的代码会打印两次"Hello":
class EventSource { public event Action MyEvent; public void Raise() => MyEvent?.Invoke(); } void Main() { var source = new EventSource(); void Handler() => Console.WriteLine("Hello"); source.MyEvent += Handler; source.MyEvent += Handler; source.Raise(); // 输出两行 "Hello" }
要是想避免重复订阅,可以在加委托之前先检查调用列表里有没有它:
if (!source.MyEvent?.GetInvocationList().Contains(Handler) ?? true) { source.MyEvent += Handler; }
最后总结一下
- 单次方法调用里,同一个本地函数创建的多个委托肯定相等,不管有没有捕获外部变量,都不用特意只搞一个实例。
- 委托的等价性就看两个点:指向的方法是不是同一个,关联的目标对象(比如闭包实例)是不是同一个,满足这俩就相等。
- 事件订阅有个坑:哪怕委托相等,多次
+=还是会触发多次执行,因为事件列表会存重复条目,要避免的话得自己提前检查。
内容的提问来源于stack exchange,提问作者relatively_random
相关产品推荐
相关产品推荐

