2013-03-05 15 views
5

我一直在使用WeakEventManager来避免内存泄漏,并且我开始过度使用它们。 我创建扩展方法,例如,用于INotifyPropertyChanged的,如:WeakEventManager拥有对用户的引用


public static void AddWeakPropertyChanged(this INotifyPropertyChanged item, Action handler) 
{ 
    PropertyChangedEventManager.AddHandler(item, (s, e) => handler(e.PropertyName), string.Empty); 
} 

现在我很快意识到,这是行不通的。事实上,你不能真正使用匿名方法进行弱事件处理。 (如果我理解正确,那么编译器为它创建一个'闭包类'(用于存放引用的值),它具有处理程序,但由于您的闭包类没有被引用到任何地方,GC将清除它,并且事件处理程序将不会被称为)

问题1:是否正确?我的意思是说它是正确的,那么当对弱事件处理程序使用匿名方法(或lambda)时,只有在GC未同时运行的情况下才会调用处理程序(例如,它是不确定的)?

那么,我想这样,所以我做了一些单元测试,以确保我得到它的权利。这似乎还好吧,直到我打了以下的单元测试:


     class DidRun 
     { 
      public bool Value { get; set; } 
     } 
     class TestEventPublisher 
     { 
      public event EventHandler<EventArgs> MyEvent; 
      public void RaiseMyEvent() 
      { 
       if (MyEvent != null) 
        MyEvent(this, EventArgs.Empty); 

      } 
     } 
     class TestClosure 
     { 
      public DidRun didRun { get; set; } 
      public EventHandler<EventArgs> Handler { get; private set; } 
      public TestClosure() 
      { 
       this.Handler = new EventHandler<EventArgs>((s, e) => didRun.Value = true); 
      } 
     } 
     [TestMethod] 
     public void TestWeakReference() 
     { 
      var raiser = new TestEventPublisher(); 
      var didrun = new DidRun(); 
      var closure = new TestClosure { didRun = didrun }; 
      WeakEventManager<TestEventPublisher, EventArgs>.AddHandler(raiser, "MyEvent", closure.Handler); 
      closure = null; 

      GC.Collect(); 
      GC.Collect(); 
      raiser.RaiseMyEvent(); 
      Assert.AreEqual(false, didrun.Value); 
     } 

问题2:任何人都可以解释我为什么会发生这种测试失败?期望:在这里,我没有任何关闭(我把它们拿出来,以确保发生了什么),我只是有一个对象(闭包),订阅了一个与WeakEventManager事件,然后我放弃引用它(closure = null;)。

我期待着2 GC.Collect()调用,以清理我的旧闭包类,所以WeakEventManager将放弃订阅者,并且不运行处理程序,但测试失败。有任何想法吗?

编辑:对不起,一般的论据是不可见的,现在他们

回答

3

你是正确的,因为GC将收集这是在你的拉姆达创建的闭包如果它没有提及。

在您的单元测试中,您将TestClosure的本地实例置空,但您已将处理程序的硬引用传递到WeakEventManager,而不是TestClosure的实例。因此,处理生活在...

我相信这些例子表明与关闭你的麻烦:

class DidRun 
{ 
    public bool Value { get; set; } 
} 

class TestEventPublisher 
{ 
    public event EventHandler<EventArgs> MyEvent; 
    public void RaiseMyEvent() 
    { 
     if (MyEvent != null) 
      MyEvent(this, EventArgs.Empty); 
    } 
} 

class TestClosure 
{ 
    static public EventHandler<EventArgs> Register(TestEventPublisher raiser, DidRun didrun) 
    { 
     EventHandler<EventArgs> handler = (s, e) => didrun.Value = true; 
     WeakEventManager<TestEventPublisher, EventArgs>.AddHandler(raiser, "MyEvent", handler); 
     return handler; 
    } 
} 

[TestMethod] 
public void Test1() 
{ 
    var raiser = new TestEventPublisher(); 
    var didrun = new DidRun(); 

    TestClosure.Register(raiser, didrun); 

    // The reference to the closure 'handler' is not being held, 
    // it may or may not be GC'd (indeterminate result) 

    raiser.RaiseMyEvent(); 
    Assert.IsTrue(didrun.Value); 
} 

[TestMethod] 
public void Test2() 
{ 
    var raiser = new TestEventPublisher(); 
    var didrun = new DidRun(); 

    // The reference to the closure 'handler' is not being held, it's GC'd 
    TestClosure.Register(raiser, didrun); 

    GC.Collect(); 
    GC.Collect(); 

    raiser.RaiseMyEvent(); 
    Assert.IsFalse(didrun.Value); 
} 

[TestMethod] 
public void Test3() 
{ 
    var raiser = new TestEventPublisher(); 
    var didrun = new DidRun(); 

    // Keep local copy of handler to prevent it from being GC'd 
    var handler = TestClosure.Register(raiser, didrun); 

    GC.Collect(); 
    GC.Collect(); 

    raiser.RaiseMyEvent(); 
    Assert.IsTrue(didrun.Value); 
} 

至于你原来的问题,你可以尝试保存处理器(关闭),以防止它被GC'd。一个ConditionalWeakTable应该对这项工作:

// ConditionalWeakTable will hold the 'value' as long as the 'key' is not marked for GC 
static private ConditionalWeakTable<INotifyPropertyChanged, EventHandler<PropertyChangedEventArgs>> _eventMapping = 
    new ConditionalWeakTable<INotifyPropertyChanged, EventHandler<PropertyChangedEventArgs>>(); 

public static void AddWeakPropertyChanged(this INotifyPropertyChanged item, Action<string> handlerAction) 
{ 
    EventHandler<PropertyChangedEventArgs> handler; 

    // Remove any existing handler for this item in case it's registered more than once 
    if (_eventMapping.TryGetValue(item, out handler)) 
    { 
     _eventMapping.Remove(item); 
     PropertyChangedEventManager.RemoveHandler(item, handler, string.Empty); 
    } 

    handler = (s, e) => handlerAction(e.PropertyName); 

    // Save handler (closure) to prevent GC 
    _eventMapping.Add(item, handler); 

    PropertyChangedEventManager.AddHandler(item, handler, string.Empty); 
} 

class DidRun 
{ 
    static public string Value { get; private set; } 
    public void SetValue(string value) { Value = value; } 
} 

[TestMethod] 
public void Test4() 
{ 
    var property = new ObservableObject<string>(); 

    var didrun = new DidRun(); 
    property.AddWeakPropertyChanged(
     (x) => 
     { 
      didrun.SetValue("Property Name = " + x); 
     }); 

    GC.Collect(); 
    GC.Collect(); 

    property.Value = "Hello World"; 

    Assert.IsTrue(DidRun.Value != null); 
} 
+0

哇,非常感谢,我被这个困惑,而事实上我忽略,即在值传递是很难参照处理。现在我明白了。再次感谢您的帮助! – MBoros 2013-10-22 10:54:52

相关问题