Có nhược điểm của việc thêm một đại biểu trống ẩn danh vào khai báo sự kiện không?


82

Tôi đã thấy một vài đề cập đến thành ngữ này (kể cả trên SO ):

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

Ưu điểm là rõ ràng - nó tránh được sự cần thiết phải kiểm tra null trước khi nâng cao sự kiện.

Tuy nhiên, tôi muốn hiểu liệu có bất kỳ nhược điểm nào không. Ví dụ, nó có phải là thứ đang được sử dụng rộng rãi và đủ minh bạch để không gây đau đầu cho việc bảo trì không? Có bất kỳ hiệu suất đáng kể nào của cuộc gọi thuê bao sự kiện trống không?

Câu 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. Ngoài ra không có hình phạt bảo trì hoặc nhược điểm 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 người đăng ký. Sự kiện với một bộ xử lý được xử lý nhiều hiệu quả hơn so với những người có hai.
supercat

46

Thay vì gây ra chi phí hiệu suất, tại sao không sử dụng một phương pháp mở rộng để 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);
    }
}

Sau khi được xác định, bạn không bao giờ phải kiểm tra lại sự kiện rỗng khác:

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

2
Xem ở đây cho một phiên bản generic: stackoverflow.com/questions/192980/...
Benjol

1
Chính xác những gì thư viện helper trinity của tôi không, tình cờ: thehelpertrinity.codeplex.com
Kent Boogaart

3
Điều này không chỉ chuyển vấn đề chuỗi của kiểm tra null vào phương thức mở rộng của bạn sao?
PatrickV

6
Không. Trình xử lý được chuyển vào phương thức, tại thời điểm đó, phiên bản đó không thể sửa đổi được.
Judah Gabriel Himango,

@PatrickV Không, Judah đúng, tham số handlertrên là tham số giá trị của phương thức. Nó sẽ không thay đổi khi phương thức đang chạy. Để điều đó xảy ra, nó sẽ cần phải là một reftham số (rõ ràng là không được phép cho một tham số có cả phần tử refvà phần thisbổ trợ), hoặc tất nhiên là một trường.
Jeppe Stig Nielsen

42

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

Dưới đây là một số con số chạy điểm chuẩn trên máy 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à mã tôi đã sử dụng để lấy những số liệu này:

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 kêu gọi thêm 5 nano giây và bạn đang cảnh báo không nên làm điều đó? Tôi không thể nghĩ ra một cách tối ưu hóa chung không hợp lý hơn thế.
Brad Wilson

8
Hấp dẫn. Theo phát hiện của bạn, kiểm tra null và gọi một đại biểu sẽ nhanh hơn là chỉ gọi nó mà không kiểm tra. Nghe có vẻ không phù hợp với tôi. Nhưng dù sao đây cũng là một sự khác biệt nhỏ đến mức tôi không nghĩ nó có thể nhận thấy được trong tất cả trừ những trường hợp khắc nghiệt nhất.
Maurice

22
Brad, tôi đã nói đặc biệt cho các hệ thống quan trọng về hiệu suất sử dụng nhiều sự kiện. Tướng đó thế nào?
Kent Boogaart

3
Bạn đang nói về hình phạt hiệu suất khi gọi các đại biểu trống. Tôi hỏi bạn: điều gì sẽ xảy ra khi ai đó thực sự đăng ký tham gia sự kiện? Bạn nên lo lắng về hiệu suất của những người đăng ký chứ không phải của những đại biểu trống.
Liviu Trifoi

2
Chi phí hiệu suất lớn không đến trong trường hợp không có người đăng ký "thực", mà là trong trường hợp có một. Trong trường hợp đó, đăng ký, hủy đăng ký và yêu cầu sẽ rất hiệu quả vì họ sẽ không phải làm bất cứ điều gì với danh sách yêu cầu, nhưng thực tế là sự kiện sẽ có hai người đăng ký thay vì một người sẽ buộc sử dụng mã xử lý "n" người đăng ký, chứ không phải mã được tối ưu hóa cho trường hợp "không" hoặc "một".
supercat

7

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

Tất nhiên, trường per-instance trong mỗi lớp sẽ vẫn có cùng một không gian.

I E

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

Ngoài ra, nó có vẻ ổn.


1
Có vẻ như từ câu trả lời ở đây: stackoverflow.com/questions/703014/… rằng trình biên dịch đã thực hiện tối ưu hóa cho một phiên bản duy nhất rồi.
Govert

3

Không có hình phạt thực hiện có ý nghĩa nào để nói về, ngoại trừ, có thể, đối với một số tình huống khắc nghiệt.

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

delegateThatCouldBeNull?.Invoke(this, value);

Ở trên, toán tử điều kiện null ?.kết hợp kiểm tra null với một lệnh gọi có điều kiện.



2

Tôi sẽ nói rằng đó là một cấu trúc hơi nguy hiểm, bởi vì nó cám dỗ bạn làm những điều như:

MyEvent(this, EventArgs.Empty);

Nếu máy khách ném một ngoại lệ, máy chủ sẽ đi theo nó.

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

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

Tuy nhiên, nếu bạn có nhiều người đăng ký và một người đăng ký có ngoại lệ, điều gì sẽ xảy ra với những người đăng ký khác?

Để đạt được điều đó, tôi đã sử dụng một số phương thức trợ giúp tĩnh thực hiện việc kiểm tra null và nuốt bất kỳ ngoại lệ nào từ phía người đăng ký (đây là từ idesign).

// 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
        { }
    }
}

Không phải để nitpick, nhưng bạn không điều gì <code> if (del == null) {return; } Delegate [] Delegate = del.GetInvocationList (); </code> có phải là ứng cử viên điều kiện chủng tộc không?
Cherian

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

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

Giải pháp của bạn cho 'điều gì xảy ra nếu một ngoại lệ được ném ra' là bỏ qua tất cả các ngoại lệ? Đây là lý do tại sao tôi luôn luôn hay hầu như luôn luôn quấn tất cả các thuê bao sự kiện trong một thử (bằng cách sử dụng tiện dụng Try.TryAction()chức năng, không phải là một rõ ràng try{}khối), nhưng tôi không bỏ qua những trường hợp ngoại lệ, tôi báo cáo ...
Dave Cousineau

0

Thay vì cách tiếp cận "đại biểu rỗng", người ta có thể xác định một phương thức mở rộng đơn giản để đóng gói phương pháp thông thường để kiểm tra trình xử lý sự kiện so với null. Nó được mô tả ở đâyở đây .


-2

Cho đến nay, một điều bị bỏ sót khi là câu trả lời cho câu hỏi này: Thật nguy hiểm nếu 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

Vấn đề 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 số năm trong quá trình sửa lỗi mã của bạn, sự kiện / trình xử lý được đặt thành null.

Luôn luôn viết séc nếu.

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

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

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

ppps: Không biết nó có còn phù hợp với luồng không ... nhưng tôi nghĩ đó là một nguyên tắc quan trọng. Vui lòng kiểm tra một chuỗi khác của stackflow


1
x.MyFunnyEvent = null; <- Điều đó thậm chí không biên dịch (bên ngoài lớp). Điểm của một sự kiện chỉ hỗ trợ + = và - =. Và bạn thậm chí không thể thực hiện x.MyFunnyEvent- = x.MyFunnyEvent bên ngoài lớp vì trình lấy sự kiện là gần như protected. Bạn chỉ có thể ngắt sự kiện khỏi chính lớp đó (hoặc từ một lớp dẫn xuất).
CodesInChaos

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

Tất nhiên nếu đại biểu của bạn ở chế độ công khai sẽ rất 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 private và bạn tự xử lý + = và - =, điều đó sẽ không thành vấn đề và kiểm tra null sẽ an toàn cho chuỗi.
ForceMagic
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.