82

私はこのイディオムについていくつかの言及を見てきました(SOを含む):

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

利点は明らかです。イベントを発生させる前に null をチェックする必要がなくなります。

ただし、欠点がある場合は理解したいと思っています。 たとえば、広く使用されていて、メンテナンスの手間がかからないほど十分に透過的なものですか? 空のイベント サブスクライバー呼び出しのパフォーマンス ヒットはかなりありますか?

4

9 に答える 9

46

パフォーマンスのオーバーヘッドを誘発する代わりに、拡張メソッドを使用して両方の問題を軽減してみませんか?

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

一度定義すると、別の null イベント チェックを再度実行する必要はありません。

// Works, even for null events.
MyButtonClick.Raise(this, EventArgs.Empty);
于 2008-11-19T18:27:45.263 に答える
42

イベントを多用し、パフォーマンスが重要なシステムの場合、少なくともこれを行わないことを検討する必要があります。空のデリゲートを使用してイベントを発生させるためのコストは、最初にnullチェックを使用して発生させる場合の約2倍です。

これが私のマシンでベンチマークを実行しているいくつかの数字です:

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

そして、これらの数字を取得するために使用したコードは次のとおりです。

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);
            }
        }
    }
}
于 2008-10-04T23:56:31.890 に答える
36

唯一の欠点は、余分な空のデリゲートを呼び出すため、パフォーマンスがわずかに低下することです。それ以外には、メンテナンスのペナルティやその他の欠点はありません。

于 2008-10-04T19:53:48.183 に答える
7

/ロット/で実行している場合は、単にデリゲート インスタンスの量を減らすために、再利用する単一の静的/共有の空のデリゲートが必要になる場合があります。いずれにせよ、コンパイラはイベントごとにこのデリゲートを (静的フィールドに) キャッシュすることに注意してください。したがって、イベント定義ごとに 1 つのデリゲート インスタンスのみであるため、大幅な節約にはなりませんが、価値があるかもしれません。

もちろん、各クラスのインスタンスごとのフィールドは、同じスペースを使用します。

すなわち

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

それ以外は問題ないようです。

于 2008-10-04T20:54:51.353 に答える
3

空のデリゲートはスレッド セーフですが、null チェックはそうではないことを理解しています。

于 2008-11-05T04:55:44.917 に答える
3

おそらくいくつかの極端な状況を除いて、話し合う意味のあるパフォーマンスの低下はありません。

ただし、C# 6.0 では、null の可能性があるデリゲートを呼び出すための代替構文が言語で提供されているため、このトリックの関連性が低くなることに注意してください。

delegateThatCouldBeNull?.Invoke(this, value);

上記の null 条件演算子?.は、null チェックと条件付き呼び出しを組み合わせています。

于 2015-08-10T18:23:23.570 に答える
2

次のようなことをしたくなるので、少し危険な構造だと思います。

MyEvent(this, EventArgs.Empty);

クライアントが例外をスローすると、サーバーはそれに従います。

それでは、多分あなたは:

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

しかし、複数のサブスクライバーがあり、1 つのサブスクライバーが例外をスローした場合、他のサブスクライバーはどうなりますか?

そのために、null チェックを実行し、サブスクライバー側からの例外を飲み込む静的ヘルパー メソッドをいくつか使用してきました (これは 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
        { }
    }
}
于 2008-11-05T05:41:17.973 に答える
0

「空のデリゲート」アプローチの代わりに、単純な拡張メソッドを定義して、null に対してイベント ハンドラーをチェックする従来の方法をカプセル化できます。ここここに 記載 され て い ます.

于 2009-03-30T19:17:52.203 に答える
-2

これまでのところ、この質問に対する答えとして見逃されていることが 1 つあります。 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

問題は、誰があなたのコードをどのように使用するかを決して知らないということです。何年かの間、コードのバグ修正中にイベント/ハンドラーが null に設定されているかどうかはわかりません。

必ずifチェックを書きます。

それが役立つことを願っています;)

ps: パフォーマンス計算ありがとうございます。

pps: イベントケースからコールバック例に編集。フィードバックをありがとう... Visual Studio を使用せずに例を「コーディング」し、考えていた例をイベントに合わせて調整しました。混乱させて申し訳ありません。

ppps: それでもスレッドに合うかどうかはわかりませんが、重要な原則だと思います。スタックフローの別のスレッドも確認してください

于 2010-10-23T08:56:05.150 に答える