添加(或删除)空事件侦听器是空操作吗?
Is adding (or removing) a null event listener a no-op?
通常可以在这里找到此类问题的答案,如果不在这里,也可以在 MSDN 上找到,但我看了又看,还是没有找到答案。实验似乎表明代码如下:
SomeControl.Click += null;
没有坏处。或者至少触发事件不会通过尝试调用空事件侦听器来抛出异常(我可以告诉)。但是,Web 上似乎没有任何地方可以证实我的希望,即这样的代码至少没有做 一些我可能不想做的事情。例如,导致 Click 事件认为它有侦听器,而实际上它可能没有,并在 "now let's perform null-checks and invoke all listeners that are not null" 代码中浪费了那些非常宝贵的超微秒。
似乎文档应该说明如果 RHS 上的侦听器为空,+= 和 -= 运算符的行为是什么。但正如我所说,我无法在任何地方找到该文档。不过,我相信这里有人可以提供它....
(显然,我的代码没有硬编码的 null;我的问题更多是关于这样的代码是浪费还是完全无害:
public static void AddHandlers([NotNull] this Button button,
[CanBeNull] EventHandler click = null,
[CanBeNull] EventHandler load = null)
{
button.Click += click;
button.Load += load;
}
或者如果我应该[即,需要出于任何原因]在每个这样的 += 操作周围添加空值检查。)
考虑这段代码:
void Main()
{
var foo = new Foo();
foo.Blah += Qaz;
foo.Blah += null;
foo.OnBlah();
}
public void Qaz()
{
Console.WriteLine("Qaz");
}
public class Foo
{
public event Action Blah;
public void OnBlah()
{
var b = Blah;
if (b != null)
{
Console.WriteLine("Calling Blah");
b();
Console.WriteLine("Called Blah");
}
}
}
它运行没有错误并产生以下输出:
Calling Blah
Qaz
Called Blah
如果我删除行 foo.Blah += Qaz;
那么代码运行时没有错误,但不会产生任何输出,因此 null
处理程序实际上被忽略了。
就 IL 而言,行 foo.Blah += null;
生成以下 IL:
IL_001A: ldloc.0 // foo
IL_001B: ldnull
IL_001C: callvirt Foo.add_Blah
IL_0021: nop
因此,它的行为与 nop
相同,但它清楚地运行了代码。
答案取决于 +=
(或 -=
)运算符是否应用于 event 或 delegate.
在您的 AddHandlers
示例中,Button
class 可能将 Click
和 Load
定义为事件,而不是代表。当它的左操作数是一个事件时,+=
运算符只调用事件的 add
访问器,而不进行任何额外的 null 检查。也就是说,在幕后 button.Click += value;
只是一个方法调用:
button.add_Click(value);
与任何其他方法一样,add_Click
如何处理空参数完全取决于 Button
class.
现在考虑 Button
class 如何实际执行 Click
事件。这是一种方法:
private EventHandler _click;
public event EventHandler Click
{
add { _click += value; }
remove { _click -= value; }
}
// Or even shorter...
// public event EventHandler Click;
// ... which is the same as above plus extra stuff for thread safety.
在此实现中,EventHandler
is a delegate type. When both operands are delegates, the +=
operator invokes Delegate.Combine
。文档说 Delegate.Combine(a, b)
returns:
A new delegate with an invocation list that concatenates the invocation lists of a and b in that order. Returns a if b is null, returns b if a is a null reference, and returns a null reference if both a and b are null references.
结论:只要 Button
class 以 "standard" 方式实现其事件(而不是,比如说,以显式 List<EventHandler>
跟踪听众),那么 button.Click += null;
最终将 不 添加空侦听器。
通常可以在这里找到此类问题的答案,如果不在这里,也可以在 MSDN 上找到,但我看了又看,还是没有找到答案。实验似乎表明代码如下:
SomeControl.Click += null;
没有坏处。或者至少触发事件不会通过尝试调用空事件侦听器来抛出异常(我可以告诉)。但是,Web 上似乎没有任何地方可以证实我的希望,即这样的代码至少没有做 一些我可能不想做的事情。例如,导致 Click 事件认为它有侦听器,而实际上它可能没有,并在 "now let's perform null-checks and invoke all listeners that are not null" 代码中浪费了那些非常宝贵的超微秒。
似乎文档应该说明如果 RHS 上的侦听器为空,+= 和 -= 运算符的行为是什么。但正如我所说,我无法在任何地方找到该文档。不过,我相信这里有人可以提供它....
(显然,我的代码没有硬编码的 null;我的问题更多是关于这样的代码是浪费还是完全无害:
public static void AddHandlers([NotNull] this Button button,
[CanBeNull] EventHandler click = null,
[CanBeNull] EventHandler load = null)
{
button.Click += click;
button.Load += load;
}
或者如果我应该[即,需要出于任何原因]在每个这样的 += 操作周围添加空值检查。)
考虑这段代码:
void Main()
{
var foo = new Foo();
foo.Blah += Qaz;
foo.Blah += null;
foo.OnBlah();
}
public void Qaz()
{
Console.WriteLine("Qaz");
}
public class Foo
{
public event Action Blah;
public void OnBlah()
{
var b = Blah;
if (b != null)
{
Console.WriteLine("Calling Blah");
b();
Console.WriteLine("Called Blah");
}
}
}
它运行没有错误并产生以下输出:
Calling Blah
Qaz
Called Blah
如果我删除行 foo.Blah += Qaz;
那么代码运行时没有错误,但不会产生任何输出,因此 null
处理程序实际上被忽略了。
就 IL 而言,行 foo.Blah += null;
生成以下 IL:
IL_001A: ldloc.0 // foo
IL_001B: ldnull
IL_001C: callvirt Foo.add_Blah
IL_0021: nop
因此,它的行为与 nop
相同,但它清楚地运行了代码。
答案取决于 +=
(或 -=
)运算符是否应用于 event 或 delegate.
在您的 AddHandlers
示例中,Button
class 可能将 Click
和 Load
定义为事件,而不是代表。当它的左操作数是一个事件时,+=
运算符只调用事件的 add
访问器,而不进行任何额外的 null 检查。也就是说,在幕后 button.Click += value;
只是一个方法调用:
button.add_Click(value);
与任何其他方法一样,add_Click
如何处理空参数完全取决于 Button
class.
现在考虑 Button
class 如何实际执行 Click
事件。这是一种方法:
private EventHandler _click;
public event EventHandler Click
{
add { _click += value; }
remove { _click -= value; }
}
// Or even shorter...
// public event EventHandler Click;
// ... which is the same as above plus extra stuff for thread safety.
在此实现中,EventHandler
is a delegate type. When both operands are delegates, the +=
operator invokes Delegate.Combine
。文档说 Delegate.Combine(a, b)
returns:
A new delegate with an invocation list that concatenates the invocation lists of a and b in that order. Returns a if b is null, returns b if a is a null reference, and returns a null reference if both a and b are null references.
结论:只要 Button
class 以 "standard" 方式实现其事件(而不是,比如说,以显式 List<EventHandler>
跟踪听众),那么 button.Click += null;
最终将 不 添加空侦听器。