C#でイベントサブスクリプションをクリアするにはどうすればよいですか?


141

次のC#クラスを取り上げます。

c1 {
 event EventHandler someEvent;
}

c1someEventイベントへのサブスクリプションがたくさんあり、それらをすべてクリアしたい場合、これを達成するための最良の方法は何ですか?また、このイベントのサブスクリプションは、ラムダ/匿名のデリゲートである可能性があることも考慮してください。

現在の解決策は、nullに設定するResetSubscriptions()メソッドを追加することです。これが目に見えない結果をもたらすかどうかはわかりません。c1someEvent

回答:


181

クラス内から、(非表示の)変数をnullに設定できます。null参照は、空の呼び出しリストを効果的に表す正規の方法です。

クラスの外からこれを行うことはできません。イベントは基本的に「購読」と「購読解除」を公開し、それだけです。

フィールドのようなイベントが実際に何をしているのかを知ることは価値があります-それらは同時に変数イベントを作成しています。クラス内では、変数を参照することになります。外部から、イベントを参照します。

詳細については、イベントとデリゲートに関する私の記事を参照してください。


3
あなたが頑固なら、反射でそれを強制的にクリアすることができます。stackoverflow.com/questions/91778/…を参照してください。
ブライアン

1
@ブライアン:実装によって異なります。それはだ場合だけ、フィールドなどのイベントやEventHandlerList、あなたのことができるようになります。ただし、これらの2つのケースを認識する必要があります。他の実装もいくつもあります。
Jon Skeet、

@ジョシュア:いいえ、変数をnullの値に設定します。変数が呼び出されないことに同意しますhidden
Jon Skeet

@JonSkeetそれは私が(思った)私が言ったことです。それが書かれた方法は5分間私を混乱させました。

@JoshuaLamusga:呼び出しリストをクリアすると言っていましたが、これは既存のオブジェクトを変更するようなものです。
Jon Skeet

34

「someEvent」をnullに設定するメソッドをc1に追加します。

public class c1
{
    event EventHandler someEvent;
    public ResetSubscriptions() => someEvent = null;    
}

それが私が見ている行動です。質問で言ったように、何か見落としているのかどうかわかりません。
プログラマー

8
class c1
{
    event EventHandler someEvent;
    ResetSubscriptions() => someEvent = delegate { };
}

null ref例外を回避するdelegate { }よりも、使用することnullをお勧めします。


2
どうして?この答えについて詳しく教えていただけますか?
S.ブダ

1
@ S.Budaそれがnullの場合、null参照を取得するためです。それはList.Clear()vs を使うようなものmyList = nullです。
オースティンWBryan

6

クラス内でイベントをnullに設定すると機能します。クラスを破棄するときは常にイベントをnullに設定する必要があります。GCはイベントに問題があり、ぶら下がりイベントがある場合、破棄されたクラスをクリーンアップしないことがあります。


6

すべてのサブスクライバーをクリアするためのベストプラクティスは、この機能を外部に公開する場合は、別のパブリックメソッドを追加してsomeEventをnullに設定することです。これには目に見えない結果はありません。前提条件は、キーワード 'event'を使用してSomeEventを宣言することを忘れないことです。

125ページの本-C#4.0の概要を参照してください。

ここで誰かがDelegate.RemoveAll方法を使用することを提案しました。これを使用する場合、サンプルコードは以下のフォームに従うことができます。しかし、それは本当に愚かです。関数SomeEvent=null内だけではないのClearSubscribers()ですか?

public void ClearSubscribers ()
{
   SomeEvent = (EventHandler) Delegate.RemoveAll(SomeEvent, SomeEvent);
   // Then you will find SomeEvent is set to null.
}

5

これは、Delegate.RemoveまたはDelegate.RemoveAllメソッドを使用して実現できます。


6
これがラムダ式や匿名のデリゲートで機能するとは思わない。
プログラマー

3

概念的な拡張退屈なコメント。

「イベント」や「デリゲート」ではなく、「イベントハンドラ」という言葉を使用します。そして、「イベント」という言葉を他のものに使用しました。一部のプログラミング言語(VB.NET、Object Pascal、Objective-C)では、「イベント」は「メッセージ」または「シグナル」と呼ばれ、「メッセージ」キーワードと特定の砂糖構文さえ持っています。

const
  WM_Paint = 998;  // <-- "question" can be done by several talkers
  WM_Clear = 546;

type
  MyWindowClass = class(Window)
    procedure NotEventHandlerMethod_1;
    procedure NotEventHandlerMethod_17;

    procedure DoPaintEventHandler; message WM_Paint; // <-- "answer" by this listener
    procedure DoClearEventHandler; message WM_Clear;
  end;

そして、その「メッセージ」に応答するために、「イベントハンドラー」が応答します。単一のデリゲートか複数のデリゲートかは関係ありません。

要約:「イベント」は「質問」、「イベントハンドラー」は答えです。


1

これは私の解決策です:

public class Foo : IDisposable
{
    private event EventHandler _statusChanged;
    public event EventHandler StatusChanged
    {
        add
        {
            _statusChanged += value;
        }
        remove
        {
            _statusChanged -= value;
        }
    }

    public void Dispose()
    {
        _statusChanged = null;
    }
}

呼び出しリストのすべてのメンバーの登録を解除Dispose()するには、using(new Foo()){/*...*/}パターンを呼び出すか使用する必要があります。


0

すべてのイベントを削除し、イベントが「アクション」タイプであると想定します。

Delegate[] dary = TermCheckScore.GetInvocationList();

if ( dary != null )
{
    foreach ( Delegate del in dary )
    {
        TermCheckScore -= ( Action ) del;
    }
}

1
イベントを宣言した型の内部にいる場合は、これを行う必要はありません。それをnullに設定できます。型の外部にいる場合は、デリゲートの呼び出しリストを取得できません。また、を呼び出すときに、イベントがnullの場合、コードは例外をスローしますGetInvocationList
2013

-1

コールバックを手動で追加および削除し、どこでも多数のデリゲート型を宣言する代わりに:

// The hard way
public delegate void ObjectCallback(ObjectType broadcaster);

public class Object
{
    public event ObjectCallback m_ObjectCallback;
    
    void SetupListener()
    {
        ObjectCallback callback = null;
        callback = (ObjectType broadcaster) =>
        {
            // one time logic here
            broadcaster.m_ObjectCallback -= callback;
        };
        m_ObjectCallback += callback;

    }
    
    void BroadcastEvent()
    {
        m_ObjectCallback?.Invoke(this);
    }
}

あなたはこの一般的なアプローチを試すことができます:

public class Object
{
    public Broadcast<Object> m_EventToBroadcast = new Broadcast<Object>();

    void SetupListener()
    {
        m_EventToBroadcast.SubscribeOnce((ObjectType broadcaster) => {
            // one time logic here
        });
    }

    ~Object()
    {
        m_EventToBroadcast.Dispose();
        m_EventToBroadcast = null;
    }

    void BroadcastEvent()
    {
        m_EventToBroadcast.Broadcast(this);
    }
}


public delegate void ObjectDelegate<T>(T broadcaster);
public class Broadcast<T> : IDisposable
{
    private event ObjectDelegate<T> m_Event;
    private List<ObjectDelegate<T>> m_SingleSubscribers = new List<ObjectDelegate<T>>();

    ~Broadcast()
    {
        Dispose();
    }

    public void Dispose()
    {
        Clear();
        System.GC.SuppressFinalize(this);
    }

    public void Clear()
    {
        m_SingleSubscribers.Clear();
        m_Event = delegate { };
    }

    // add a one shot to this delegate that is removed after first broadcast
    public void SubscribeOnce(ObjectDelegate<T> del)
    {
        m_Event += del;
        m_SingleSubscribers.Add(del);
    }

    // add a recurring delegate that gets called each time
    public void Subscribe(ObjectDelegate<T> del)
    {
        m_Event += del;
    }

    public void Unsubscribe(ObjectDelegate<T> del)
    {
        m_Event -= del;
    }

    public void Broadcast(T broadcaster)
    {
        m_Event?.Invoke(broadcaster);
        for (int i = 0; i < m_SingleSubscribers.Count; ++i)
        {
            Unsubscribe(m_SingleSubscribers[i]);
        }
        m_SingleSubscribers.Clear();
    }
}

質問をフォーマットして、左側の空白をすべて削除していただけますか?IDEからコピーして貼り付けると、これが発生する可能性があります
AustinWBryan

ちょうどその白いスペースを取り除いた、私の悪い
バルダモン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.