2013-02-11 13 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

這是使用匿名方法作爲事件處理程序的限制。它們不能像普通方法那樣被刪除(這在技術上是委託實例通過方法組轉換自動創建的),因爲匿名方法被編譯到編譯器生成的容器類中,並且每次創建該類的新實例。

爲了保留動作參數,您可以創建一個容器類,該容器類可以爲您的事件處理程序提供委託。這個類可以在你正在使用的其他類中被聲明爲私有的,或者是內部的,也可以是「Helpers」命名空間。它看起來像這樣:

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

然後,在您的類中創建並存儲容器的引用。您可以創建一個靜態成員currentContainer然後設置這樣的處理程序:

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 '可以爲每個屬性名稱保存一個委託。 (是的,那是一種不同類型的容器。) – hvd 2013-02-11 17:11:15

+0

是的,我只是想提出一個通用的解決方案。我隱含地試圖傳達的是,帶有閉包的匿名方法在包含對捕獲變量的引用的幕後生成一個容器類。所以基本上這是同一類型的行爲,但你會明確表示...... – 2013-02-11 17:30:26

+0

非常感謝。有用!!! 「PropertyChangedEventHandler」只會觸發一次。 – Nadya 2013-02-12 09:07:44

0

從技術上講,這不是你想退訂相同的匿名方法。 .NET每次調用OnPropertyChanged時都會創建該方法的新實例。這就是爲什麼退訂不起作用。

+0

這不一定只是技術上不同的方法。如果出於某種原因使用同一個'propertyName'的不同'字符串'實例,它*有*是不同的方法。 – hvd 2013-02-11 17:08:23

+0

@ hvd好點 – Anri 2013-02-11 18:15:04

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

上述代碼充當「one and done」事件處理程序。您可以註冊無限數量的這些數據,並且每個數據只會在註銷之前執行一次。

請記住,如果您在多個線程中短時間連續地引發事件,可能會多次執行其中一個處理程序。爲防止出現這種情況,您可能需要創建一個靜態的映射對象實例來「鎖定對象」,並添加一些哨兵代碼以確保只處理一次處理程序。但是,這些實現細節似乎超出了目前編寫的問題範圍。

+0

這是不同的行爲。這只是讓它被解僱一次,而OP的代碼卻試圖確保只有一個處理器(但它可以被多次觸發)。 – Servy 2013-02-11 16:54:29

+1

哦,並注意如果這個事件是在很短的時間間隔內從多個線程觸發的,那麼它可能會多次運行。 – Servy 2013-02-11 16:56:11

+0

所有優點。可能需要靜態的對象 - >處理程序字典,以將每個對象實例限制爲單個事件處理程序。 – BTownTKD 2013-02-11 16:57:44