2014-09-03 62 views
4

我不喜歡不符合標準的模式,但我正在對我的應用程序進行快速測試,並且碰到了這種奇怪的行爲。爲什麼WeakEventManager在發送者不是名義值時不會觸發事件?

考慮一個暴露事件的普通類,這裏是非常常見的PropertyChanged,但我認爲可以是任何其他類。

用戶選擇通過WeakEventManager助手訂閱事件。現在,「奇怪」的事情是實際的發件人引用:只要實例與訂閱時使用的實例相同,一切都會正常進行。但是,當您使用其他對象時,不會發出通知。

再一次,這是不是一個很好的模式,但我想知道是否有任何充分的理由這種限制,或者說,這是一種錯誤。好奇心比真正的需要更多。

class Class1 
{ 
    static void Main(string[] args) 
    { 
     var c = new MyClass(); 

     WeakEventManager<INotifyPropertyChanged, PropertyChangedEventArgs>.AddHandler(
      c, 
      "PropertyChanged", 
      Handler 
      ); 

     c.ActualSender = c; 
     c.Number = 123; //will raise 

     c.ActualSender = new Class1(); 
     c.Number = 456; //won't raise 

     Console.ReadKey(); 
    } 

    static void Handler(object sender, PropertyChangedEventArgs e) 
    { 
     Console.WriteLine("Handled!"); 
    } 
} 

class MyClass : INotifyPropertyChanged 
{ 
    public object ActualSender { get; set; } 


    private int _number; 
    public int Number 
    { 
     get { return this._number; } 
     set 
     { 
      if (this._number != value) 
      { 
       this._number = value; 
       this.OnPropertyChanged("Number"); 
      } 
     } 
    } 


    public event PropertyChangedEventHandler PropertyChanged; 

    private void OnPropertyChanged(
     string name 
     ) 
    { 
     this.PropertyChanged(
      this.ActualSender, 
      new PropertyChangedEventArgs(name) 
      ); 
    } 
} 

編輯:這裏是一個粗略的方法來實現預期的行爲(爲了簡單起見,硬鏈接)。

class Class1 
{ 
    static void Main(string[] args) 
    { 
     var cx = new MyClass(); 
     var cy = new MyClass(); 

     Manager.AddHandler(cx, Handler1); 
     Manager.AddHandler(cx, Handler2); 
     Manager.AddHandler(cy, Handler1); 
     Manager.AddHandler(cy, Handler2); 

     cx.ActualSender = cx; 
     cx.Number = 123; 

     cx.ActualSender = new Class1(); 
     cx.Number = 456; 

     cy.ActualSender = cy; 
     cy.Number = 789; 

     cy.ActualSender = new Class1(); 
     cy.Number = 555; 

     Console.ReadKey(); 
    } 

    static void Handler1(object sender, PropertyChangedEventArgs e) 
    { 
     var sb = new StringBuilder(); 
     sb.AppendFormat("Handled1: {0}", sender); 

     var c = sender as MyClass; 
     if (c != null) sb.AppendFormat("; N={0}", c.Number); 
     Console.WriteLine(sb.ToString()); 
    } 

    static void Handler2(object sender, PropertyChangedEventArgs e) 
    { 
     var sb = new StringBuilder(); 
     sb.AppendFormat("Handled2: {0}", sender); 

     var c = sender as MyClass; 
     if (c != null) sb.AppendFormat("; N={0}", c.Number); 
     Console.WriteLine(sb.ToString()); 
    } 
} 

static class Manager 
{ 
    private static Dictionary<object, Proxy> _table = new Dictionary<object, Proxy>(); 

    public static void AddHandler(
     INotifyPropertyChanged source, 
     PropertyChangedEventHandler handler 
     ) 
    { 
     var p = new Proxy(); 
     p._publicHandler = handler; 
     source.PropertyChanged += p.InternalHandler; 
     _table[source] = p; 
    } 

    class Proxy 
    { 
     public PropertyChangedEventHandler _publicHandler; 
     public void InternalHandler(object sender, PropertyChangedEventArgs args) 
     { 
      this._publicHandler(sender, args); 
     } 
    } 
} 
+0

您是否檢查過'WeakEventManager'中的'NewListenerList'來查看仍然引用了哪些偵聽器 – 2014-09-03 09:50:07

+0

檢查該方法看起來有點複雜,但是我嘗試使用memory-profiler並且似乎從未調用過(雖然不確定)。 – 2014-09-03 10:10:23

回答

3

我還沒有發現,說這事的任何文件,但你可以看看WeakEventManager source code看到爲什麼會這樣。

管理器保持一個表將註冊的源對象映射到其處理程序。請注意,此源對象是您在添加處理程序時傳入的對象。

當管理器收到一個事件時,它從該表中查找相關的處理程序,並使用該事件的發件人作爲鍵。顯然,如果此發件人與註冊的發件人不相同,則不會找到預期的處理程序。


編輯

下面是一些僞代碼來說明。

public class PseudoEventManager : IWeakEventListener 
{ 
    private static PseudoEventManager _instance = new PseudoEventManager(); 

    private readonly Dictionary<object, List<object>> _handlerTable 
          = new Dictionary<object, List<object>>(); 

    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) 
    { 
     foreach (var handler in _handlerTable[sender]) // point of interest A 
      //invoke handler 
    } 

    public static void AddHandler(object source, object handler) 
    { 
     if (!_instance._handlerTable.ContainsKey(source)) 
      _instance._handlerTable.Add(source, new List<object>()); //point of interest B 
     _instance._handlerTable[source].Add(handler); 
     //attach to event 
    } 
} 

添加處理程序時,傳入的源將被添加到查找表中。當收到一個事件時,查詢此表以查找此事件的發件人,以獲取此發件人/來源的相關處理程序。

在您的示例中,您正在偵聽的源是c,這也是第一次也是ActualSender的值。因此,事件的發送者與註冊的源相同,這意味着處理程序被正確地找到並被調用。

然而,第二次ActualSender是與c不同的實例。註冊的源不會更改,但sender參數的值現在有所不同!因此它將無法檢索處理程序,並且什麼都不會被調用。

+0

好的,但這仍然很奇怪。保持每個發件人的WeakReference不會更簡單,並在發件人死亡時將其扔掉?說真的,我看不出有任何理由這麼做。 – 2015-03-27 04:42:20

+0

我沒有看到任何問題:我爲弱多聽衆可觀察的來源做了類似的事情。只需註冊一組聽衆,而不是一個。我從這篇優秀的文章開始:http://blog.stephencleary.com/2009/07/nitoweakreference-and.html – 2015-03-27 07:58:29

+0

我的意思是說你說的,但是 - 我看不到問題。經理充當提升事件的實例的代理監聽器,對吧?因此,「AddHandler」方法應訂閱事件併爲偵聽器公開弱集合。然後,實例引發一個事件,並由代理接收。現在,因爲任何CLR事件都會繼承EventArgs,所以它會在代理中攜帶「sender」字段。在這點上,爲什麼代理在將事件轉發給實際監聽器時未使用此「發件人」引用? – 2015-03-27 08:54:29