由于我的原始答案似乎存在争议,我决定做一些测试,包括查看生成的代码和监控性能。
首先,这是我们的测试平台,一个带有委托的类和另一个使用它的类:
class EventProducer
{
public void Raise()
{
var handler = EventRaised;
if (handler != null)
handler(this, EventArgs.Empty);
}
public event EventHandler EventRaised;
}
class Counter
{
long count = 0;
EventProducer producer = new EventProducer();
public void Count()
{
producer.EventRaised += CountEvent;
producer.Raise();
producer.EventRaised -= CountEvent;
}
public void CountWithNew()
{
producer.EventRaised += new EventHandler(CountEvent);
producer.Raise();
producer.EventRaised -= new EventHandler(CountEvent);
}
private void CountEvent(object sender, EventArgs e)
{
count++;
}
}
首先要做的是查看生成的 IL:
.method public hidebysig instance void Count() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
L_0006: ldarg.0
L_0007: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
L_000d: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
L_0012: callvirt instance void DelegateTest.Program/EventProducer::add_EventRaised(class [mscorlib]System.EventHandler)
L_0017: ldarg.0
L_0018: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
L_001d: callvirt instance void DelegateTest.Program/EventProducer::Raise()
L_0022: ldarg.0
L_0023: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
L_0028: ldarg.0
L_0029: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
L_002f: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
L_0034: callvirt instance void DelegateTest.Program/EventProducer::remove_EventRaised(class [mscorlib]System.EventHandler)
L_0039: ret
}
.method public hidebysig instance void CountWithNew() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
L_0006: ldarg.0
L_0007: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
L_000d: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
L_0012: callvirt instance void DelegateTest.Program/EventProducer::add_EventRaised(class [mscorlib]System.EventHandler)
L_0017: ldarg.0
L_0018: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
L_001d: callvirt instance void DelegateTest.Program/EventProducer::Raise()
L_0022: ldarg.0
L_0023: ldfld class DelegateTest.Program/EventProducer DelegateTest.Program/Counter::producer
L_0028: ldarg.0
L_0029: ldftn instance void DelegateTest.Program/Counter::CountEvent(object, class [mscorlib]System.EventArgs)
L_002f: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
L_0034: callvirt instance void DelegateTest.Program/EventProducer::remove_EventRaised(class [mscorlib]System.EventHandler)
L_0039: ret
}
所以事实证明,是的,它们确实生成了相同的 IL。原来我错了。但这不是全部。可能是我在这里跑题了,但我认为在谈论事件和代表时包含这一点很重要:
创建和比较不同的代表并不便宜。
当我写这篇文章时,我认为第一个语法能够将方法组转换为委托,但事实证明这只是一个转换。但是当您真正保存委托时,情况就完全不同了。如果我们将其添加到消费者:
class Counter
{
EventHandler savedEvent;
public Counter()
{
savedEvent = CountEvent;
}
public void CountSaved()
{
producer.EventRaised += savedEvent;
producer.Raise();
producer.EventRaised -= savedEvent;
}
}
您可以看到,这在性能方面与其他两个具有非常不同的特征:
static void Main(string[] args)
{
const int TestIterations = 10000000;
TimeSpan countTime = TestCounter(c => c.Count());
Console.WriteLine("Count: {0}", countTime);
TimeSpan countWithNewTime = TestCounter(c => c.CountWithNew());
Console.WriteLine("CountWithNew: {0}", countWithNewTime);
TimeSpan countSavedTime = TestCounter(c => c.CountSaved());
Console.WriteLine("CountSaved: {0}", countSavedTime);
Console.ReadLine();
}
static TimeSpan TestCounter(Action<Counter> action, int iterations)
{
var counter = new Counter();
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < TestIterations; i++)
action(counter);
sw.Stop();
return sw.Elapsed;
}
结果始终以类似于以下内容的形式返回:
Count: 00:00:02.4742007
CountWithNew: 00:00:02.4272702
CountSaved: 00:00:01.9810367
在使用保存的委托与创建新委托时,这几乎有 20% 的差异。
现在显然不是每个程序都会在这么短的时间内添加和删除这么多委托,但是如果您正在编写库类 - 可能以您无法预测的方式使用的类 - 那么您真的想要如果您需要添加 和删除 事件,请牢记这一差异(我个人已经编写了很多代码)。
因此得出的结论是,编写 SomeEvent += new EventHandler(NamedMethod) 编译为与 SomeEvent += NamedMethod 相同的内容。但是,如果您打算稍后删除该事件处理程序,您确实应该保存委托。尽管 Delegate 类有一些特殊情况代码允许您从您添加的委托中删除一个引用不同的委托,但它必须做大量的工作才能实现这一点。
如果您不打算保存委托,那么它没有任何区别 - 编译器最终会创建一个新委托。