c# - 为什么这个通用 C# WeakEvent 监听器不起作用?

标签 c# generics design-patterns weak-references

我试图通过开发一个通用的弱事件监听器来节省时间,我可以只传递一个要采取的操作。一切似乎都进展顺利,直到有人取消注册为止。这似乎取消了所有这些的注册。我感到困惑的是为什么,这与为 IWeakEventListener 参数传递 this 有何不同?

  public class GenericWeakEventListener : IWeakEventListener
  {
    #region EventAction

    /// <summary>
    /// Action to take for the event
    /// </summary>
    private Action<Type, object, EventArgs> _eventAction;

    /// <summary>
    /// Gets or sets the action to take for the event
    /// </summary>
    //[DataMember]
    public Action<Type, object, EventArgs> EventAction
    {
      get
      {
        return _eventAction;
      }

      private set
      {
        if (EventAction != value)
        {
          _eventAction = value;
        }
      }
    }

    #endregion EventAction

    #region Constructors

    public GenericWeakEventListener(Action<Type, object, EventArgs> action)
    {
      EventAction = action;
    }

    #endregion Constructors

    #region Public Methods

    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
      if (EventAction != null)
      {
        EventAction(managerType, sender, e);
      }

      return true;
    }

    #endregion Public Methods
  }

编辑:

这是监听器代码:

  public class SomeClient
  {
    public int ID { get; set; }

    private Timer timer = null;
    private Timer timer2 = null;

    public SomeClient(int id, SomeService service)
    {
      ID = id;
      //EventHandler<GenericEventArgs<string>> d = (o, s) => Console.WriteLine("Client {0}: {1}", ID, s.Item);
      if (service != null) SomeEventChangedEventManager.AddListener(service, new GenericWeakEventListener((t, s, e) => { Console.WriteLine("SomeEvent: " + ID); }));

      timer = new Timer { AutoReset = true, Interval = 1000 };
      SomeTimerElapsedEventManager.AddListener(timer, new GenericWeakEventListener((t, s, e) => { Console.WriteLine("SomeTimer: " + ID); }));
      timer.Start();
    }
  }

这是发布者的代码:

  public class SomeService
  {
    public event EventHandler<GenericEventArgs<string>> SomeEvent;

    public SomeService()
    {
      System.Timers.Timer timer = new Timer { AutoReset = true, Interval = 1000 };
      timer.Elapsed += (sender, args) => { if (SomeEvent != null) SomeEvent(this, new GenericEventArgs<string>(Guid.NewGuid().ToString())); };
      timer.Start();
    }
  }

这是主方法中的代码:

public static void Main(string[] args)
{
  SomeService service = new SomeService();
  List<SomeClient> clients = new List<SomeClient>();

  // Build clients
  for (int x = 0; x < 5; x++)
  {
    clients.Add(new SomeClient(x + 1, service));
  }

  System.Timers.Timer timer = new Timer { AutoReset = true, Interval = 5000 };
  timer.Elapsed += (s, a) =>
    {
      if (clients.Count == 0)
      {
        return;
      }

      Console.WriteLine("Removing\r\n");
      clients.RemoveAt(0);
      GC.Collect();
    };
  timer.Start();

  Console.ReadLine();
}

这是输出: 一些事件:1 一些事件:2 一些事件:3 一些事件:4 一些事件:5 某个计时器:2 某个计时器:3 某个计时器:4 某个计时器:1 某个计时器:5 一些事件:1 一些事件:2 一些事件:3 一些事件:4 一些事件:5 某个计时器:1 某个计时器:2 某个计时器:3 某个计时器:4 某个计时器:5 一些事件:1 一些事件:2 一些事件:3 一些事件:4 一些事件:5 某个计时器:2 某个计时器:3 某个计时器:4 某个计时器:5 某个计时器:1 一些事件:1 一些事件:2 一些事件:3 一些事件:4 一些事件:5 某个计时器:1 某个计时器:2 某个计时器:3 某个计时器:5 某个计时器:4 一些事件:1 一些事件:2 一些事件:3 一些事件:4 一些事件:5 正在删除

如果没有通用弱事件监听器,则输出将在没有 1 的情况下继续,然后在没有 2 的情况下继续,依此类推。

最佳答案

从OP编辑中编辑:明白了。我认为正在发生的事情是这样的:

您正在声明 SomeClient 拥有的 lambda,并包含实例变量 ID 的外部闭包。这使得 lambda(通过 SomeEventChangedEventManager 作为委托(delegate)传递给 SomeService 上的事件)依赖于该 ID 实例的持续存在。

当您删除该 SomeClient 实例时,该 lambda 所需的 ID 变量将超出范围并被 GC 处理。但是,我没有看到此代码的任何部分从 SomeService 的 SomeEvent 的处理程序中删除此 lambda。因此,lambda 仍作为对匿名委托(delegate)的引用保留在内存中,但它所依赖的其他数据现已消失。这会导致运行时抛出异常,该异常以某种方式被吞噬并且不会导致整个程序崩溃。

但是,该事件基本上按照附加的顺序执行处理程序委托(delegate)(这是他们通常告诉您忽略的实现细节)已停止执行,因为其中一个处理程序已抛出。这使得删除第一个客户端看起来已经删除了所有客户端,而实际上这些客户端的处理程序只是因为第一个处理程序出错而没有执行。

解决办法有两个:

  • 定义 lambda 并将其存储为 SomeClient 的实例变量。这允许您保留对它的引用,这很重要,因为在确定相等性时不会对委托(delegate)进行语义比较,因此以下代码不起作用:

    SomeEvent += (a,b,c) => Foo(a,b,c);
    //the following line will not remove the handler added in the previous line,
    //because the two lambdas are compiled into differently-named methods
    //and so this is a different reference to a different method.
    SomeEvent -= (a,b,c) => Foo(a,b,c);
    
  • 在 SomeClient 上实现 IDisposable 和/或终结器。当从列表中删除客户端时,GC 调用的处理程序/终结器应该从 SomeEvent 监听器中删除该实例的 lambda(可能通过 Manager 上的 RemoveListener() 方法)。因为您保留了对委托(delegate)的引用,该委托(delegate)准确指向添加的内容,所以处理程序将被删除,因此不会被执行,也不会出错。

关于c# - 为什么这个通用 C# WeakEvent 监听器不起作用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10435006/

相关文章:

c# - 如何在 WPF 中创建类似 Windows 8 样式的进度环?

java - Java 中的构造泛型类

typescript - 如何在 TypeScript 中定义泛型数组?

Java 接口(interface)、创建者模式和 "getInstance()"方法或等效方法

c# - Data Transfer Object 中的属性应该扩展外键还是简单地公开它们的主键

c# - VS 团队测试 : . 以 Excel 作为数据源的网络单元测试:适配器失败

c# - 在 IE8 中,chrome 下拉列表的宽度与下拉框的宽度相同,而在 IE9 中,下拉列表的宽度不同。如何修复?

c# - 从 Windows 窗体控件中按名称查找控件

c# - 与 Func<in T1, out TResult> 作为参数的协变/逆变

java - 使用 Guice 注入(inject)许多装饰实例——关于将 HessianServlet 与 Guice 混合