10

私がやりたいことは、関数の名前を知らなくても、基本的にイベントから関数を削除することです。

私は持っていFileSystemWatcherます。ファイルが作成/名前変更されると、その名前がチェックされます。一致する場合は、特定の場所に移動します。ただし、ファイルがロックされている場合は、タイマーの tick イベントにアタッチするラムダを作成し、ファイルがロックされなくなるまで待機します。そうでない場合は、ファイルを移動し、イベント ハンドラーから自身を削除します。インスタンスを保持したり、名前付きメソッドを作成したりするなど、これを行う多くの方法を見てきました。ここではどちらもできません。私のオプションは何ですか?

4

2 に答える 2

30

これを達成する簡単な方法はありません。

推奨されるアプローチ:

デリゲートを保存できない理由がわかりません。インスタンスをフィールドとして保存する必要はありません。これは、匿名イベント ハンドラーによってキャプチャされるローカル変数にすることができます。

EventHandler<TypeOfEventArgs> handler = null;
handler = (s, e) =>
{
    // Do whatever you need to do here

    // Remove event:
    foo.Event -= handler;
}

foo.Event += handler;

これを使用できない単一のシナリオは考えられません。

デリゲートを保存しない代替アプローチ:

ただし、そのようなシナリオがある場合は、非常に注意が必要です。
イベントのハンドラーとして追加されたデリゲートを見つける必要があります。保存していないため、入手がかなり困難です。this現在実行中のメソッドのデリゲートを取得する必要はありません。

GetInvocationList()で定義されているクラス外のイベントへのアクセスは、ハンドラーの追加と削除、つまり+=とに制限されているため、イベントで使用することもできません-=

新しいデリゲートを作成することもできません。匿名メソッドを定義するオブジェクトにはアクセスできMethodInfoますが、メソッドが宣言されているクラスのインスタンスにはアクセスできません。このクラスはコンパイラによって自動的に生成されthis、匿名メソッド内で呼び出すと、のインスタンスが返されます。通常のメソッドが定義されているクラス。

私が見つけた唯一の方法は、イベントが使用するフィールドを見つけて呼び出すGetInvocationList()ことです。次のコードは、ダミー クラスを使用してこれを示しています。

void Main()
{
    var foo = new Foo();
    foo.Bar += (s, e) => {
        Console.WriteLine("Executed");
        
        var self = new StackFrame().GetMethod();
        var eventField = foo.GetType()
                            .GetField("Bar", BindingFlags.NonPublic | 
                                             BindingFlags.Instance);
        if(eventField == null)
            return;
        var eventValue = eventField.GetValue(foo) as EventHandler;
        if(eventValue == null)
            return;
        var eventHandler = eventValue.GetInvocationList()
                                     .OfType<EventHandler>()
                                     .FirstOrDefault(x => x.Method == self)
                               as EventHandler;
        if(eventHandler != null)
            foo.Bar -= eventHandler;
    };
    
    foo.RaiseBar();
    foo.RaiseBar();
}

public class Foo
{
    public event EventHandler Bar;
    public void RaiseBar()
    { 
        var handler = Bar;
        if(handler != null)
            handler(this, EventArgs.Empty);
    }
}

"Bar"に渡される文字列は、イベントで使用されるフィールドGetFieldの正確な名前である必要があることに注意してください。これにより、次の 2 つの問題が発生します。

  1. 明示的なイベント実装を使用する場合など、フィールドには別の名前を付けることができます。フィールド名を手動で見つける必要があります。
  2. フィールドがまったくない場合もあります。これは、イベントが明示的なイベント実装を使用し、単に別のイベントに委任するか、他の方法で委任を格納する場合に発生します。

結論:

代替アプローチは実装の詳細に依存するため、回避できる場合は使用しないでください。

于 2013-05-24T12:59:17.883 に答える