2008-10-04 20 views
78

Tôi đã thấy một vài đề cập đến các thành ngữ này (bao gồm on SO):Có nhược điểm nào khi thêm đại biểu trống ẩn danh vào khai báo sự kiện không?

// Deliberately empty subscriber 
public event EventHandler AskQuestion = delegate {}; 

Ngược ở đây là rõ ràng - nó tránh sự cần thiết để kiểm tra null trước khi tăng sự kiện này.

Tuy nhiên, tôi rất muốn hiểu nếu có bất kỳ nhược điểm nào. Ví dụ, nó là một cái gì đó được sử dụng rộng rãi và đủ minh bạch rằng nó sẽ không gây ra một nhức đầu bảo trì? Có bất kỳ hoạt động đánh giá cao nào về cuộc gọi người đăng ký sự kiện trống không?

Trả lời

36

Nhược điểm duy nhất là một hình phạt hiệu suất rất nhỏ khi bạn đang gọi thêm đại biểu trống. Khác hơn là không có hình phạt bảo trì hoặc hạn chế khác.

+19

Nếu sự kiện sẽ có chính xác một người đăng ký (một trường hợp * rất phổ biến), trình xử lý giả sẽ làm cho nó có hai. Sự kiện với một trình xử lý được xử lý * nhiều * hiệu quả hơn so với những người có hai. – supercat

7

Nếu bạn đang làm nó a/lot /, bạn có thể muốn có một đại biểu trống, tĩnh/chia sẻ mà bạn sử dụng lại, chỉ đơn giản là để giảm khối lượng các phiên bản ủy nhiệm. Lưu ý rằng trình biên dịch lưu trữ ủy nhiệm này cho mỗi sự kiện anyway (trong một trường tĩnh), do đó, nó chỉ là một cá thể đại biểu cho mỗi định nghĩa sự kiện, vì vậy nó không phải là tiết kiệm lớn - nhưng có thể đáng giá.

Trường thể hiện trong mỗi lớp sẽ vẫn chiếm cùng một không gian, tất nhiên.

ví dụ:

internal static class Foo 
{ 
    internal static readonly EventHandler EmptyEvent = delegate { }; 
} 
public class Bar 
{ 
    public event EventHandler SomeEvent = Foo.EmptyEvent; 
} 

Khác hơn thế, nó có vẻ tốt đẹp.

+1

Có vẻ như từ câu trả lời ở đây: http://stackoverflow.com/questions/703014/will-an-empty-delegate-eat-up-memory rằng trình biên dịch thực hiện tối ưu hóa cho một cá thể đã có. – Govert

41

Đối với hệ thống sử dụng nhiều sự kiện và có hiệu suất quan trọng, bạn chắc chắn sẽ muốn ít nhất xem xét không làm điều này. Chi phí cho việc nâng cao một sự kiện với một đại biểu trống là gần gấp hai lần để nâng cao nó với một kiểm tra null trước.

Dưới đây là một số nhân vật chạy chuẩn trên máy tính của tôi:

For 50000000 iterations . . . 
No null check (empty delegate attached): 530ms 
With null check (no delegates attached): 249ms 
With null check (with delegate attached): 452ms 

Và đây là đoạn code tôi đã sử dụng để có được những con số:

using System; 
using System.Diagnostics; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     public event EventHandler<EventArgs> EventWithDelegate = delegate { }; 
     public event EventHandler<EventArgs> EventWithoutDelegate; 

     static void Main(string[] args) 
     { 
      //warm up 
      new Program().DoTimings(false); 
      //do it for real 
      new Program().DoTimings(true); 

      Console.WriteLine("Done"); 
      Console.ReadKey(); 
     } 

     private void DoTimings(bool output) 
     { 
      const int iterations = 50000000; 

      if (output) 
      { 
       Console.WriteLine("For {0} iterations . . .", iterations); 
      } 

      //with anonymous delegate attached to avoid null checks 
      var stopWatch = Stopwatch.StartNew(); 

      for (var i = 0; i < iterations; ++i) 
      { 
       RaiseWithAnonDelegate(); 
      } 

      stopWatch.Stop(); 

      if (output) 
      { 
       Console.WriteLine("No null check (empty delegate attached): {0}ms", stopWatch.ElapsedMilliseconds); 
      } 


      //without any delegates attached (null check required) 
      stopWatch = Stopwatch.StartNew(); 

      for (var i = 0; i < iterations; ++i) 
      { 
       RaiseWithoutAnonDelegate(); 
      } 

      stopWatch.Stop(); 

      if (output) 
      { 
       Console.WriteLine("With null check (no delegates attached): {0}ms", stopWatch.ElapsedMilliseconds); 
      } 


      //attach delegate 
      EventWithoutDelegate += delegate { }; 


      //with delegate attached (null check still performed) 
      stopWatch = Stopwatch.StartNew(); 

      for (var i = 0; i < iterations; ++i) 
      { 
       RaiseWithoutAnonDelegate(); 
      } 

      stopWatch.Stop(); 

      if (output) 
      { 
       Console.WriteLine("With null check (with delegate attached): {0}ms", stopWatch.ElapsedMilliseconds); 
      } 
     } 

     private void RaiseWithAnonDelegate() 
     { 
      EventWithDelegate(this, EventArgs.Empty); 
     } 

     private void RaiseWithoutAnonDelegate() 
     { 
      var handler = EventWithoutDelegate; 

      if (handler != null) 
      { 
       handler(this, EventArgs.Empty); 
      } 
     } 
    } 
} 
+11

Bạn đang đùa, phải không? Lời gọi thêm 5 nano giây và bạn đang cảnh báo chống lại việc đó? Tôi không thể nghĩ ra một sự tối ưu hóa không hợp lý nhiều hơn thế. –

+8

Thú vị. Theo những phát hiện của bạn, kiểm tra null và gọi đại biểu nhanh hơn chỉ để gọi nó mà không cần kiểm tra. Nghe không đúng với tôi. Nhưng dù sao thì đây là một sự khác biệt nhỏ đến mức tôi không nghĩ nó đáng chú ý trong tất cả trừ những trường hợp cực đoan nhất. – Maurice

+22

Brad, tôi đã nói cụ thể cho các hệ thống quan trọng về hiệu năng, sử dụng nhiều sự kiện. Làm thế nào mà nói chung? –

2

Đó là sự hiểu biết của tôi rằng các đại biểu có sản phẩm nào là chủ đề an toàn, trong khi kiểm tra null thì không.

+2

Không có mẫu nào làm cho chuỗi sự kiện an toàn như được nêu trong câu trả lời ở đây: http://stackoverflow.com/questions/10282043/is-this-a-better-way-to-fire-invoke-events-without -a-null-check-in-c – Beachwalker

2

Tôi có thể nói đó là một chút của một cấu trúc nguy hiểm, bởi vì nó cám dỗ bạn làm điều gì đó như:

MyEvent(this, EventArgs.Empty); 

Nếu khách hàng ném một ngoại lệ, máy chủ đi với nó.

Vì vậy, sau đó, có lẽ bạn cần làm:

try 
{ 
    MyEvent(this, EventArgs.Empty); 
} 
catch 
{ 
} 

Nhưng, nếu bạn có nhiều thuê bao và một thuê bao ném một ngoại lệ, những gì xảy ra đối với các thuê bao khác?

Để kết thúc, tôi đã sử dụng một số phương pháp trợ giúp tĩnh thực hiện kiểm tra rỗng và nuốt bất kỳ ngoại lệ nào từ phía bên thuê bao (đây là từ thiết kế).

// Usage 
EventHelper.Fire(MyEvent, this, EventArgs.Empty); 


public static void Fire(EventHandler del, object sender, EventArgs e) 
{ 
    UnsafeFire(del, sender, e); 
} 
private static void UnsafeFire(Delegate del, params object[] args) 
{ 
    if (del == null) 
    { 
     return; 
    } 
    Delegate[] delegates = del.GetInvocationList(); 

    foreach (Delegate sink in delegates) 
    { 
     try 
     { 
      sink.DynamicInvoke(args); 
     } 
     catch 
     { } 
    } 
} 
+0

Không để nitpick, nhưng không bạn điều if (del == null) { return; } Delegate[] delegates = del.GetInvocationList(); là một ứng cử viên điều kiện chủng tộc? – Cherian

+0

Không hoàn toàn. Vì các đại biểu là các kiểu giá trị, del thực sự là một bản sao riêng của chuỗi đại biểu chỉ có thể truy cập được đối với thân phương thức UnsafeFire. (Caveat: Điều này thất bại nếu UnsafeFire được inlined, vì vậy bạn cần sử dụng thuộc tính [MethodImpl (MethodImplOptions.NoInlining)] để chống lại nó.) –

+0

1) Các đại biểu là các kiểu tham chiếu 2) Chúng không thay đổi, vì vậy nó không phải là điều kiện cuộc đua 3) Tôi không nghĩ nội tuyến sẽ thay đổi hành vi của mã này. Tôi hy vọng rằng tham số del sẽ trở thành một biến cục bộ mới khi được inlined. – CodesInChaos

46

Thay vì gây hiệu suất trên cao, tại sao không use an extension method để giảm bớt cả hai vấn đề:

public static void Raise(this EventHandler handler, object sender, EventArgs e) 
{ 
    if(handler != null) 
    { 
     handler(sender, e); 
    } 
} 

Khi đã xác định, bạn không bao giờ phải làm một kiểm tra sự kiện vô lại:

// Works, even for null events. 
MyButtonClick.Raise(this, EventArgs.Empty); 
+8

Trong thực tế, điều này thuộc về ngôn ngữ hoặc khuôn khổ! –

+2

Xem ở đây để biết phiên bản chung: http://stackoverflow.com/questions/192980/boiler-plate-code-replacement-is-there-anything-bad-about-this-code – Benjol

+1

Chính xác thư viện trợ giúp của tôi có ba thứ gì, một cách ngẫu nhiên: http://thehelpertrinity.codeplex.com/ –

0

Thay vào đó của cách tiếp cận "đại diện rỗng", người ta có thể định nghĩa một phương thức mở rộng đơn giản để đóng gói phương thức thông thường của việc kiểm tra trình xử lý sự kiện đối với null. Nó được mô tả herehere.

-1

Một điều bị bỏ qua là câu trả lời cho câu hỏi này cho đến thời điểm này: Thật nguy hiểm khi tránh kiểm tra giá trị null.

public class X 
{ 
    public delegate void MyDelegate(); 
    public MyDelegate MyFunnyCallback = delegate() { } 

    public void DoSomething() 
    { 
     MyFunnyCallback(); 
    } 
} 


X x = new X(); 

x.MyFunnyCallback = delegate() { Console.WriteLine("Howdie"); } 

x.DoSomething(); // works fine 

// .. re-init x 
x.MyFunnyCallback = null; 

// .. continue 
x.DoSomething(); // crashes with an exception 

Điều này là: Bạn không bao giờ biết ai sẽ sử dụng mã của bạn theo cách nào. Bạn không bao giờ biết, nếu trong một vài năm trong khi sửa lỗi mã của bạn, sự kiện/trình xử lý được đặt thành rỗng.

Luôn, viết nếu kiểm tra.

Hy vọng điều đó sẽ giúp;)

ps: Cảm ơn bạn đã tính toán hiệu suất.

pps: Đã chỉnh sửa từ trường hợp sự kiện và ví dụ gọi lại. Cảm ơn các thông tin phản hồi ... Tôi "mã hóa" ví dụ w/o Visual Studio và điều chỉnh ví dụ tôi đã có trong tâm trí đến một sự kiện. Xin lỗi vì sự nhầm lẫn.

ppps: Không biết nếu nó vẫn phù hợp với chủ đề ... nhưng tôi nghĩ rằng đó là một nguyên tắc quan trọng. Ngoài ra, vui lòng kiểm tra another thread of stackflow

+1

x.MyFunnyEvent = null; <- Điều đó thậm chí không biên dịch (bên ngoài lớp học). Điểm của sự kiện chỉ hỗ trợ + = và - =. Và bạn thậm chí không thể làm x.MyFunnyEvent- = x.MyFunnyEvent bên ngoài lớp học kể từ khi getter sự kiện được bán 'bảo vệ'. Bạn chỉ có thể phá vỡ sự kiện từ chính lớp đó (hoặc từ một lớp dẫn xuất). – CodesInChaos

+0

bạn đúng ... đúng cho các sự kiện ... đã có một trường hợp với một trình xử lý đơn giản. Lấy làm tiếc. Tôi sẽ cố gắng chỉnh sửa. – Thomas

+0

Tất nhiên nếu đại biểu của bạn công khai sẽ nguy hiểm vì bạn không bao giờ biết người dùng sẽ làm gì. Tuy nhiên, nếu bạn đặt các đại biểu trống trên một biến riêng và bạn xử lý + = và - = chính mình, điều đó sẽ không thành vấn đề và kiểm tra null sẽ là luồng an toàn. – ForceMagic

2

Không có hình phạt hiệu suất có ý nghĩa để nói về, ngoại trừ, có thể, đối với một số trường hợp cực đoan.

Lưu ý, tuy nhiên, thủ thuật này trở nên ít liên quan trong C# 6.0, bởi vì ngôn ngữ cung cấp một cú pháp thay thế cho các đại biểu gọi có thể được null:

delegateThatCouldBeNull?.Invoke(this, value); 

Trên, null có điều kiện khai thác ?. kết hợp kiểm tra null với một lời gọi có điều kiện.

Các vấn đề liên quan