2013-02-11 25 views
7

INotifyPropertyChangedを実装するオブジェクトのPropertyChangedイベントを購読する拡張メソッドがあります。静的メソッド(拡張メソッド)内の匿名イベントハンドラの登録解除

私はこのイベントが一度だけ起こりたいと思います。それ以上はない。

これは私の方法です。

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) 
{ 
    if (target == null) 
    { 
     return; 
    } 

    PropertyChangedEventHandler handler = (obj, e) => 
    { 

     if (propertyName == e.PropertyName) 
     { 
      action(); 
     } 

    }; 


    target.PropertyChanged -= handler; 
    target.PropertyChanged += handler; 

} 

しかし、動作しません。このメソッドを呼び出すたびにイベントが発生するように、イベントハンドラを削除することはできません。

私は別のアプローチを試みました。

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) 
{ 
    if (target == null) 
    { 
     return; 
    } 

    target.PropertyChanged -= target_PropertyChanged; 
    target.PropertyChanged += target_PropertyChanged; 

} 

static void target_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     //do stuff here 
    } 

名前のついたメソッドを使用する代わりに、次のような伝統的な方法を使用します。イベントは一度だけ発生しますが、Actionパラメータも必要です。私はこのアプローチでは使用できません。

この問題を解決するには、回避策または別の方法がありますか?静的メソッド内に匿名メソッドがありますか?

ありがとうございます。

答えて

3

これは、匿名メソッドをイベントハンドラとして使用する場合の制限です。匿名メソッドはコンパイラ生成のコンテナクラスにコンパイルされ、毎回そのクラスの新しいインスタンスが作成されるため、通常のメソッド(これは技術的にはデリゲートインスタンスはメソッドグループ変換を介して自動的に作成されます)では削除できません。

アクションパラメータを保持するには、内部にイベントハンドラのデリゲートを持つコンテナクラスを作成することができます。クラスは、あなたが作業している他のクラスの内部でプライベート宣言することもできますし、おそらく内部の "ヘルパー"名前空間にすることもできます。

class DelegateContainer 
{ 
    public DelegateContainer(Action theAction, string propName) 
    { 
     TheAction = theAction; 
     PopertyName = propName; 
    } 

    public Action TheAction { get; private set; } 
    public string PropertyName { get; private set; } 

    public void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) 
    { 
     if(PropertyName == e.PropertyName) 
      TheAction(); 
    } 
} 

次に、クラスにコンテナへの参照を作成して保存します。それはあなたが退会しようとしている同じ匿名メソッドではありません、

private static DelegateContainer currentContainer; 

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) 
{ 
    if (target == null) 
    { 
     return; 
    } 

    if(currentContainer != null)   
     target.PropertyChanged -= currentContainer.PropertyChangedHandler; 

    currentContainer = new DelegateContainer(action, propertyName); 
    target.PropertyChanged += currentContainer.PropertyChangedHandler; 
} 
+0

+1コンテナクラスの考え方ですが、これは不必要に複雑です: 'Dictionary 'はプロパティ名ごとに1つのデリゲートを保持できます。 (はい、これは別の種類のコンテナクラスです) – hvd

+0

ええ、私は汎用ソリューションを提示したかっただけです。私が暗黙に伝えようとしていたのは、クロージャを持つ匿名メソッドが、キャプチャされた変数への参照を含むシーンの背後にあるコンテナクラスを生成するということです。だから本質的には同じ種類の行動ですが、明示的にしています。 –

+0

ありがとうございます。できます!!! 'PropertyChangedEventHandler'は一度起動します。 – Nadya

0

技術的に:あなたは、静的メンバcurrentContainerを作成し、このようなハンドラを設定できます。 .NETは、OnPropertyChangedが呼び出されるたびにそのメソッドの新しいインスタンスを作成します。そのため、登録を解除することはできません。

+0

必ずしも技術的に異なる方法であるとは限りません。何らかの理由で同じ 'propertyName'の異なる' string'インスタンスが使用された場合、*は別のメソッドになります。 – hvd

+0

@hvd良い点 – Anri

3

から、イベントハンドラ自体に退会した場合、最初の例を得ることができます。

public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action) 
{ 
    if (target == null) 
    { 
     return; 
    } 

    // Declare the handler first, in order to create 
    // a concrete reference that you can use from within 
    // the delegate 
    PropertyChangedEventHandler handler = null; 
    handler = (obj, e) => 
    { 
     if (propertyName == e.PropertyName) 
     { 
      obj.PropertyChanged -= handler; //un-register yourself 
      action(); 
     } 

    }; 
    target.PropertyChanged += handler; 
} 

上記のコードは、「1つの完了済み」イベントハンドラとして機能します。これらは無制限に登録することができ、それぞれを一度だけ実行してから登録解除します。

イベントを複数のスレッドにわたって連続して発生させると、これらのハンドラの1つを複数回実行できることに注意してください。これを防ぐには、オブジェクトをロックする静的Dictionary(T,T)マッピングオブジェクトインスタンスを作成し、ハンドラが一度だけ実行されるようにいくつかのセントリコードを追加する必要があります。これらの実装の詳細は現在書かれているようにあなたの質問の範囲外です。

+0

これは異なる動作です。 OPのコードではハンドラが1つだけであることを確認しようとしていましたが(しかし、複数回起動することができます)、これは1回だけ解雇されるようにしています。 – Servy

+1

ああ、このイベントが短い時間間隔で複数のスレッドから発生した場合、複数のスレッドを実行できる可能性があります。 – Servy

+0

良い点があります。オブジェクトの各インスタンスを単一のイベントハンドラに制限するには、objects-> handlersの静的ディクショナリが必要な場合があります。 – BTownTKD

関連する問題