25

イベントハンドラがnullかどうかをチェックするとき、これはスレッドごとに行われますか?

誰かがそのイベントを確実に聞いているようにするには、次のようにします。

EventSeven += new DivBySevenHandler(dbsl.ShowOnScreen);

上記のパターンに従ってnullをチェックする場所にコードを追加すると、なぜnullチェックが必要になるのでしょうか。このサイトから取得したコード)何が足りないの?

また、イベントとGCに関する規則は何ですか?


6 답변


47

あなたが私が恐れているとはどういう意味かはっきりしませんが、デリゲートがnullになる可能性がある場合は、各スレッドで個別に確認する必要があります。通常は次のようにします。

public void OnSeven()
{
    DivBySevenHandler handler = EventSeven;
    if (handler != null)
    {
        handler(...);
    }
}

これにより、EventSevenの過程で変化するOnSeven()あなたは得ることはありませんNullReferenceException

しかし、あなたが間違いなくサブスクライブされたハンドラを持っているならば、あなたはあなたがnullチェックを必要としないのは正しいです。これは、 "no-op"ハンドラを使ってC#2で簡単に実行できます。

public event DivBySevenHandler EventSeven = delegate {};

一方、あなたはたぶんさまざまなスレッドからサブスクリプションを取得する可能性がある場合に、確実に「最新の」一連のハンドラーを取得していることを確認するために、ある種のロックを使用します。を持っています私のスレッディングチュートリアルの例それは助けになることができます - 通常私はそれを必要としないようにすることを試みることをお勧めしますが。

ガベージコレクションの面では、イベント出版社イベントへの参照で終わる加入者(つまり、ハンドラのターゲット)これは、発行者が購読者よりも長生きすることを意図している場合にのみ問題になります。


  • (OP /リスト用)最後のポイントについては、これは特に静的イベントの場合はtrue(静的フィールドはコレクションの対象にならないため)。このような理由から、静的イベントは避けるのが最善です。 - Marc Gravell
  • 無効なデリゲートと同じセマンティクスを持つデリゲートのEventDelegateカテゴリに問題があったのではないかと思います。ただし、nullのEventDelegateの呼び出しが明示的にnopとして定義されることはありません。 - supercat
  • どうですか?EventSeven?.Invoke(....)。それはスレッドセーフですか? - Hossein Narimani Rad
  • @HosseinNarimaniRad:答えの例と同じくらい安全です。 C#のメモリモデルはそれほど明確ではありませんが、基本的には安全だと思います。 - Jon Skeet

51

問題は、誰もそのイベントを購読していない場合、それがnullであることです。そして、あなたはnullに対して呼び出すことはできません。 3つのアプローチが頭に浮かぶ。

  • nullをチェックします(下記参照)
  • 「何もしない」ハンドラを追加します。public event EventHandler MyEvent = delegate {};
  • 拡張方法を使う(下記参照)

nullをチェックするとき、スレッドセーフにするためには、理論的には最初にデリゲート参照をキャプチャします(チェックと呼び出しの間で変化する場合)。

protected virtual void OnMyEvent() {
    EventHandler handler = MyEvent;
    if(handler != null) handler(this, EventArgs.Empty);
}

拡張メソッドは、nullインスタンスで呼び出すことができるという珍しい性質を持っています...

    public static void SafeInvoke(this EventHandler handler, object sender)
    {
        if (handler != null) handler(sender, EventArgs.Empty);
    }
    public static void SafeInvoke<T>(this EventHandler<T> handler,
        object sender, T args) where T : EventArgs
    {
        if (handler != null) handler(sender, args);
    }

それからあなたは呼び出すことができます:

MyEvent.SafeInvoke(this);

そしてそれはnullチェック(チェックによる)とスレッドセーフ(参照を一度だけ読むことで)の両方です。


  • スタック上の暗黙のコピーによるスレッドの安全性のように - ShuggyCoUk
  • いい説明です。 " read by only"と言った場合は、スレッドセーフです。読み取りが完了すると、ターゲット(読み取り対象のオブジェクト)が自由に変更できるためです。しかし、どのようにして「参照を1回読む」のですか。たぶん私はただ何かについていけなかった… - dotnetdev
  • @dotnetdev - これをメソッドの引数として渡しているため。現在値を読み取るスタックに。 SafeInvokeの内部では、スタック上にコピーが表示されているだけです(オリジナルは200回更新できますが、表示されることはありません)。ここでは、デリゲートが不変であることを助けます。 - Marc Gravell
  • ...購読を解除すると、新しいデリゲートが作成され、新しいインスタンスを指すようにフィールドが変更されます。 「if(MyEvent!= null)MyEvent(...)」とは対照的です。 - 明らかに2つの読みがあります。 - Marc Gravell
  • @VoodooChildは、日曜日のクラス分けをするときに、自分のイベントを購読することをお勧めします。継承時にオーバーライドすることをお勧めします。それがすべてです。 - Marc Gravell

23

C#6.0構文に関する短い情報を追加したいと思います。

これを置き換えることが可能になりました:

var handler = EventSeven;

if (handler != null)
    handler.Invoke(this, EventArgs.Empty);

これとともに:

handler?.Invoke(this, EventArgs.Empty);


と組み合わせる表現体メンバー次のコードを短くすることができます。

protected virtual void OnMyEvent()
{
    EventHandler handler = MyEvent;
    handler?.Invoke(this, EventArgs.Empty);
}

までワンライナー

protected virtual void OnMyEvent() => MyEvent?.Invoke(this, EventArgs.Empty);


null条件演算子の詳細については、MSDNを参照してください。

見るこのブログ表現体メンバーについて



2

イベントハンドラを起動する前にチェックすることは常に良い習慣です。たとえ私が最初にそれが常に設定されていることを私自身に「保証」したとしても、私はこれをする。後でこれを変更した場合、イベントの発生をすべてチェックする必要はありません。そのため、各イベントには常に次のようなOnXXXメソッドが付随しています。

private void OnEventSeven()
{
    var handler = EventSeven;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}

外部の呼び出し元が自由にイベントハンドラを追加したり削除したりできるので、イベントハンドラがクラスに対してパブリックである場合、これは特に重要です。


  • これはスレッドセーフではありません。最後のハンドラが「if」の後に退会した場合評価された場合は、NullReferenceExceptionが発生する可能性があります。 - Jon Skeet
  • それでは、これほど多くの人がスレッドと国連の安全性を確認できなかったのはどうしてでしょう。モーニングコールをありがとう! - Peter Lillevold
  • 私たちの多くが同じ(反)パターンを使っているようです。しかしこの場合、PeterはMarcが行ったようにしています(参照をキャプチャしますが、暗黙の型を使用しています)。 Marcが書いたものとPeterが書いたものの違いは何ですか。 - dotnetdev
  • @Jon Skeet - 上記のMarc Gravellを繰り返しているだけなので、他の人はこれがスレッドセーフではないと確信していません。デリゲートは不変なので、ハンドラがソースイベントからサブスクライブ解除されると、新しいオブジェクトが作成されます。イベントから作成されたコピーは、元のデリゲートを参照しています。 - Reed Rector
  • @Reed:スレッドセーフです、 はい。コメントを書いたときには編集ログを確認していませんでした。 - Jon Skeet

-1

あなたがこれを意味するならば:

public static void OnEventSeven(DivBySevenEventArgs e)
    {
        if(EventSeven!=null)
            EventSeven(new object(),e);
    }    

コードの一部、そして答えは:

誰も "EventSeven"イベントハンドラを購読していない場合は、 "EventSeven(new object()、e);"でnull参照例外が発生します。

そして規則:

サブスクライバは、イベントを受信したくなくなったときにハンドラを追加し(+ =)、削除します( - =)。オブジェクトが参照されなくなった場合、ガベージコレクションはデフォルトの規則に従って処理されます。


  • これはスレッドセーフではありません。最後のハンドラが「if」の後に退会した場合評価された場合は、NullreferenceExceptionが発生する可能性があります。 - Jon Skeet
  • 本当...この例はあちこちにあり(そして私もそれを使用しています)、これのスレッドの安全性にまだ立ち止まっていませんでした! - thijs
  • 余談ですが、送信者がイベントを開始するオブジェクトになることは慣例ではありません(例:thisランダムに新しく作成されたオブジェクトではなく) - Rowland Shaw
  • @Rowland - 同意しました - Marc Gravell
  • 「オブジェクトが参照されなくなった場合」 ...それ自体が衝突の対象にならないオブジェクトによって。 aがbを参照し、bがaを参照する場合、他に何もそれらを見ることができない場合、それらは両方ともまだ収集の対象となります。 - Marc Gravell

-1

を使うポストシャープコンパイル後の手順でコンパイル済みアセンブリを調整することは可能です。これにより、コードに「アスペクト」を適用して、分野横断的な問題を解決することができます。

nullチェックや空のデリゲートの初期化は非常に小さな問題かもしれませんが、アセンブリ内のすべてのイベントに空のデリゲートを追加することで解決するという側面を書きました。

使い方はとても簡単です。

[assembly: InitializeEventHandlers( AttributeTargetTypes = "Main.*" )]
namespace Main
{
   ...
}

私のブログで詳細にアスペクトについて議論しました。あなたがPostSharpを持っている場合には、これがアスペクトです:

/// <summary>
///   Aspect which when applied on an assembly or class, initializes all the event handlers (<see cref="MulticastDelegate" />) members
///   in the class(es) with empty delegates to prevent <see cref="NullReferenceException" />'s.
/// </summary>
/// <author>Steven Jeuris</author>
[AttributeUsage( AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Event )]
[MulticastAttributeUsage( MulticastTargets.Event, AllowMultiple = false )]
[AspectTypeDependency( AspectDependencyAction.Commute, typeof( InitializeEventHandlersAttribute ) )]
[Serializable]
public class InitializeEventHandlersAttribute : EventLevelAspect
{
    [NonSerialized]
    Action<object> _addEmptyEventHandler;


    [OnMethodEntryAdvice, MethodPointcut( "SelectConstructors" )]
    public void OnConstructorEntry( MethodExecutionArgs args )
    {
        _addEmptyEventHandler( args.Instance );
    }

    // ReSharper disable UnusedMember.Local
    IEnumerable<ConstructorInfo> SelectConstructors( EventInfo target )
    {
        return target.DeclaringType.GetConstructors( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
    }
    // ReSharper restore UnusedMember.Local

    public override void RuntimeInitialize( EventInfo eventInfo )
    {
        base.RuntimeInitialize( eventInfo );

        // Construct a suitable empty event handler.
        MethodInfo delegateInfo = DelegateHelper.MethodInfoFromDelegateType( eventInfo.EventHandlerType );
        ParameterExpression[] parameters = delegateInfo.GetParameters().Select( p => Expression.Parameter( p.ParameterType ) ).ToArray();
        Delegate emptyDelegate
            = Expression.Lambda( eventInfo.EventHandlerType, Expression.Empty(), "EmptyDelegate", true, parameters ).Compile();

        // Create a delegate which adds the empty handler to an instance.
        _addEmptyEventHandler = instance => eventInfo.AddEventHandler( instance, emptyDelegate );
    }
}

...そしてそれが使用するヘルパーメソッド:

/// <summary>
///   The name of the Invoke method of a Delegate.
/// </summary>
const string InvokeMethod = "Invoke";


/// <summary>
///   Get method info for a specified delegate type.
/// </summary>
/// <param name = "delegateType">The delegate type to get info for.</param>
/// <returns>The method info for the given delegate type.</returns>
public static MethodInfo MethodInfoFromDelegateType( Type delegateType )
{
    Contract.Requires( delegateType.IsSubclassOf( typeof( MulticastDelegate ) ), "Given type should be a delegate." );

    return delegateType.GetMethod( InvokeMethod );
}

リンクされた質問


関連する質問

最近の質問