2010-05-01 94 views
61

可能重複:
C#: Difference between ‘ += anEvent’ and ‘ += new EventHandler(anEvent)’+ =新的EventHandler(方法)VS + =方法

有兩種基本方式來訂閱事件:

SomeEvent += new EventHandler<ArgType> (MyHandlerMethod); 
SomeEvent += MyHandlerMethod; 

有什麼區別,我應該什麼時候選擇一個呢?

編輯:如果它是相同的,那麼爲什麼VS默認爲長版本,混亂的代碼?這對我來說毫無意義。

+6

代碼生成器經常混亂(使用長限定名稱而不是使用指令)。它們旨在簡化生成並避免錯誤,而不是爲了可讀性。 – 2010-05-01 14:45:39

+1

我正在專門討論代碼段,它並未被自動化代碼生成器使用。 – mafu 2010-05-01 16:34:06

+1

我也是如此。長形式不太可能產生歧義。 – 2010-05-01 17:52:12

回答

35

由於我的原始答案似乎存在一些爭議,因此我決定做一些測試,包括查看生成的代碼監控性能。

首先,這裏是我們的測試牀,一個代表和其他類的類來使用它:

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 

,使用保存d時,幾乎是一個20%差異elegate與創建一個新的。

現在顯然不是每個程序都會在這麼短的時間內添加和刪除這麼多的代表,但是如果你正在編寫圖書館類 - 可能以你無法預測的方式使用的類 - 那麼你真的如果您需要添加並刪除事件(並且我已經編寫了大量代碼,可以親自執行此操作),請記住這一區別。

因此,這個結論是,寫作SomeEvent += new EventHandler(NamedMethod)編譯爲只有SomeEvent += NamedMethod相同的東西。但如果你打算刪除事件處理程序後,你真的應該保存委託。儘管Delegate類有一些特殊代碼,允許您從添加的代碼中刪除不同於指定代理的代理,但它必須執行一些不重要的工作來解決此問題。

如果你不打算保存委託,那麼它沒有什麼區別 - 編譯器最終會創建一個新的委託。

+0

這是完全錯誤的(並且只能部分糾正)。這兩種形式沒有區別,既不適用於'+ ='或' - =''。對於同樣的事物,它們只是不同的符號。 – 2010-05-01 14:36:07

+0

@Aero,我讀了鏈接的東西,它以一個示例結束,這兩個表單相同,並不能證明一種形式更快或更好。 – 2010-05-01 14:50:33

+2

只是爲了確定我寫了一個小測試,看了看IL。這兩種形式產生__identical__ IL代碼。 – 2010-05-01 15:02:11

2

沒有什麼區別,第一個定義更具體。

24

從編程的角度來看,它們彼此都是等價的。編譯器幾乎可以完成你在第一行和第二行後面完成的工作。所以我會一直選擇第二種方法(少代碼)。

回覆:您的編輯

大概是因爲他們覺得它更好地展示開發者做的事情,而不是快捷方式的適當方式。你的猜測和我的一樣好:)

7

沒有什麼區別。在.NET 2.0之前,每個變量賦值都必須是確切類型,編譯器則沒有太多推論。爲了解決這個問題,VS 2003圍繞函數名發出new EventHandler。這只是我的猜測。因爲..

我在VS 2008,textBox1.KeyDown += (KeyEventHandler)textBox1_KeyDown,現在也嘗試了一些東西,也工作。它讓我困惑,爲什麼他們選擇new EventHandler(checkBox1_CheckStateChanged),而不是(EventHandler)checkBox1_CheckStateChanged。但...

因爲我沒有VS 2003在我的箱子了,我不能停下來,如果鑄造方法也可以在VS 2003上工作。但afaict,我試圖刪除new EventHandler函數名稱時,我使用VS 2003(.NET 1.1),認爲爲什麼需要實例化(new EventHandler)的一個函數,委託只是函數指針而已,但它不起作用。

僅僅從.NET 2.0開始,C#編譯器纔開始儘可能地推斷。

本文http://blueonionsoftware.com/blog.aspx?p=aed2ae46-7548-4e5f-83c6-95e00c6f3649支持我的new EventHandler之前.NET 2.0編譯器的內存,這是一個必須放

[編輯]

下面的文章繼續深入探討訂閱/ unsubcribing事件,聲稱有button1.Click += new EventHandler(button1_Click);button1.Click += button1_Click;,但可悲的之間的差異,我不能看到IL水平雖然:-(

http://blogs.msdn.com/abhinaba/archive/2005/08/26/456437.aspx

任何區別
+0

不,長版本不是解決方法,它是完整的語法。另一個是編譯器重寫爲長版本的簡寫。 – 2010-05-01 12:23:24

+0

@亨克Holterman:我刪除了我的答案,然後我重新編輯了我的答案。我並不確定那是不是強制性的,所以在我嘗試寫出我能記得的和沒有刪除的內容之前,我先試着寫一篇支持我記得的東西的文章。長的形式是強制性的,但是我不能否定它是否解決了編譯器的顯式類型分配兼容性問題,我現在開始學習ildasm :-)我不知道實例指令在IL I中做了什麼現在檢查 – 2010-05-01 13:23:59

+0

在.NET 1.1和之前的版本中,長表單是必要的。 C#2引入了簡短的表單。隨着匿名方法,邁向lambdas的一步。 – 2010-05-01 14:38:45

8

第二種形式是在c#的更高版本中引入的語法糖。第一行將在每個版本中工作