イベント宣言に匿名の空のデリゲートを追加することの欠点はありますか?


82

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

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

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

しかし、マイナス面があるかどうかを知りたいと思っています。 たとえば、広く使用されており、メンテナンスの頭痛の種を引き起こさないほど十分に透明なものですか?空のイベントサブスクライバーコールのパフォーマンスにかなりの影響はありますか?

回答:


36

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


19
それ以外の場合、イベントにサブスクライバーが1つだけある場合(非常に一般的なケース)、ダミーハンドラーによって2つになります。1つのハンドラーを持つイベントは、2つのハンドラーを持つイベントよりもはるかに効率的に処理されます。
スーパーキャット2012年

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);

2
:ジェネリックバージョンについてはこちらを参照してくださいstackoverflow.com/questions/192980/...
Benjol

1
ちなみに、私のヘルパートリニティライブラリが行うこととまったく同じ
Kent Boogaart 2012年

3
これは、nullチェックのスレッドの問題を拡張メソッドに移動するだけではありませんか?
PatrickV 2013

6
いいえ。ハンドラーはメソッドに渡されます。その時点では、そのインスタンスは変更できません。
Judah Gabriel Himango 2013

@PatrickVいいえ、ユダは正しいhandlerです。上のパラメーターはメソッドの値パラメーターです。メソッドの実行中は変更されません。そのためには、refパラメーター(明らかに、パラメーターにrefthis修飾子の両方を含めることは許可されていません)、またはもちろんフィールドである必要があります。
Jeppe Stig Nielsen 2014

42

イベントを多用し、パフォーマンスが重要なシステムの場合、少なくともこれを行わないことを検討する必要があります。空のデリゲートでイベントを発生させるためのコストは、最初にヌルチェックで発生させる場合の約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);
            }
        }
    }
}

11
からかってるんだろ?呼び出しは5ナノ秒を追加し、それを行うことに対して警告していますか?それ以上に不合理な一般的な最適化は考えられません。
ブラッドウィルソン

8
面白い。調査結果によると、nullをチェックしてデリゲートを呼び出す方が、チェックなしで呼び出すよりも高速です。私には正しく聞こえません。しかし、とにかく、これは非常に小さな違いであるため、最も極端な場合を除いて、目立たないと思います。
モーリス

22
ブラッド、私は特に、イベントを多用するパフォーマンスが重要なシステムについて述べました。それはどのように一般的ですか?
ケントブオゴール2008年

3
空のデリゲートを呼び出すときのパフォーマンスのペナルティについて話している。私はあなたに尋ねます:誰かが実際にイベントを購読するとどうなりますか?空のデリゲートではなく、サブスクライバーのパフォーマンスについて心配する必要があります。
Liviu Trifoi 2011年

2
「実際の」サブスクライバーがゼロの場合ではなく、サブスクライバーが1つある場合、大きなパフォーマンスコストが発生します。その場合、サブスクリプション、サブスクリプション解除、および呼び出しは、呼び出しリストに対して何もする必要がないため、非常に効率的ですが、イベントが(システムの観点から)1つではなく2つのサブスクライバーを持つという事実により、使用が強制されます「ゼロ」または「1」の場合に最適化されたコードではなく、「n」サブスクライバーを処理するコードの。
スーパーキャット2012年

7

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

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

すなわち

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

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


1
ここでの答えから、stackoverflow.com / questions / 703014 / コンパイラがすでに単一のインスタンスに対して最適化を行っているようです。
Govert 2011

3

いくつかの極端な状況を除いて、話すべき意味のあるパフォーマンスペナルティはありません。

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

delegateThatCouldBeNull?.Invoke(this, value);

上記のnull条件演算子?.は、nullチェックと条件呼び出しを組み合わせたものです。



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

ちっぽけなことではありませんが、<code> if(del == null){return; } Delegate []デリゲート= del.GetInvocationList(); </ code>は競合状態の候補ですか?
Cherian 2008年

完全ではありません。デリゲートは値型であるため、delは実際にはデリゲートチェーンのプライベートコピーであり、UnsafeFireメソッド本体にのみアクセスできます。(警告:UnsafeFireがインライン化されると失敗するため、[MethodImpl(MethodImplOptions.NoInlining)]属性を使用してそれを無効にする必要があります。)
Daniel Fortunov 2009年

1)デリゲートは参照型です2)不変であるため、競合状態ではありません3)インライン化によってこのコードの動作が変わるとは思いません。インライン化すると、パラメーターdelが新しいローカル変数になると思います。
CodesInChaos 2010年

「例外がスローされた場合に何が起こるか」に対するあなたの解決策は、すべての例外を無視することです。これが、私が常にまたはほとんど常にすべてのイベントサブスクリプションを試行でラップする理由です(Try.TryAction()明示的なtry{}ブロックではなく、便利な関数を使用して)が、例外を無視せず、報告します...
Dave Cousineau 2016

0

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


-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:イベントケースからコールバックの例に編集しました。フィードバックをありがとう...私はVisualStudioなしで例を「コーディング」し、私が考えていた例をイベントに合わせて調整しました。混乱させて申し訳ありません。

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


1
x.MyFunnyEvent = null; <-それはコンパイルすらしません(クラスの外で)。イベントのポイントは、+ =と-=のみをサポートしています。また、イベントゲッターは準であるため、クラスの外でx.MyFunnyEvent- = x.MyFunnyEventを実行することもできませんprotected。イベントは、クラス自体(または派生クラス)からのみ中断できます。
CodesInChaos 2010年

あなたは正しいです...イベントに当てはまります...単純なハンドラーの場合がありました。ごめんなさい。編集してみます。
トーマス

もちろん、デリゲートが公開されている場合、ユーザーが何をするかわからないため、危険です。ただし、プライベート変数に空のデリゲートを設定し、+ =と-=を自分で処理する場合、それは問題にはならず、nullチェックはスレッドセーフになります。
ForceMagic 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.