内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用
在代码审查中,我偶然发现了这个(简化的)代码片段来取消注册一个事件处理程序:
Fire -= new MyDelegate(OnFire);
我认为这不会取消注册事件处理程序,因为它会创建一个以前从未注册过的新代理程序。但是搜索MSDN我发现了几个使用这个习惯用法的代码示例。
所以我开始了一个实验:
internal class Program
{
public delegate void MyDelegate(string msg);
public static event MyDelegate Fire;
private static void Main(string[] args)
{
Fire += new MyDelegate(OnFire);
Fire += new MyDelegate(OnFire);
Fire("Hello 1");
Fire -= new MyDelegate(OnFire);
Fire("Hello 2");
Fire -= new MyDelegate(OnFire);
Fire("Hello 3");
}
private static void OnFire(string msg)
{
Console.WriteLine("OnFire: {0}", msg);
}
}
令我惊讶的是,发生了以下情况:
Fire("Hello 1");
如预期的那样产生了两条消息。Fire("Hello 2");
产生了一条消息!Fire("Hello 3");
扔了一个NullReferenceException
。
我知道,对于事件处理程序和委托,编译器会在场景后面生成大量代码。但我仍然不明白为什么我的推理是错误的。
我错过了什么?
你应该始终检查委托在启动之前是否没有目标(其值为空)。如前所述,这样做的一种方式是订阅一个不会被删除的不做任何匿名方法。
public event MyDelegate Fire = delegate {};
但是,这只是一个避免NullReferenceExceptions的黑客攻击。
还有一种解决方法是将委托复制到临时变量中:
public event MyDelegate Fire;
public void FireEvent(string msg)
{
MyDelegate temp = Fire;
if (temp != null)
temp(msg);
}
不幸的是,JIT编译器可能会优化代码,消除临时变量,并使用原始代理。
所以为了避免这个问题,你可以使用接受委托作为参数的方法:
[MethodImpl(MethodImplOptions.NoInlining)]
public void FireEvent(MyDelegate fire, string msg)
{
if (fire != null)
fire(msg);
}
请注意,如果没有MethodImpl(NoInlining)属性,JIT编译器可能会使内联方法变得毫无价值。你可以使用这个方法:
FireEvent(Fire,"Hello 3");
建议网络测试可使用一个组1个节点配置,根据组织多少来定测试规模;企业应用以1个组织2个节点为基础起步,保持组织内的高可用,节点数量扩展根据组织数量和交易使用量增加,一个区块链网络内建议最多不超过100个节点。谢谢您的提问