挂钩列表中所有对象的事件

时间:2011-07-11 17:01:24

标签: c# .net reflection

问题: 我发现自己经常想要处理一组对象上的事件。在从集合中添加和删除对象时,必须挂钩或取消挂钩每个对象。我发现这对于设计每个使用相同事件挂钩代码的类来说是乏味和重复的。

期望的解决方案:所以,我试图提出类似EventBindingList的内容,其中包含可挂钩的对象,并允许用户一次挂钩多个对象,并且添加和删​​除列表中的对象。

为了保持通用性,有必要使用Reflection。在列表的构造函数中,用户可以通过EventInfo或Event name指定要挂接的事件。这似乎是最简单的方法。

    private EventInfo _info;

    public EventBindingList(string EventName)
    {
        _info = typeof(T).GetEvents().Where(e => e.Name == EventName).First();
    }

    public EventBindingList(EventInfo info)
    {
        _info = info;
    }

我尝试了几种方法,但我仍然遇到方法,委托,lambdas和EventHandlers之间的差异问题。

解决方案1失败

我尝试过的一个无效的解决方案是使用自定义事件访问器。这将是包含要挂钩的对象的列表上的事件。这是因为,在添加EventHandler时,抛出了ArgumentException:Object of type 'System.EventHandler' cannot be converted to type 'ExternalProject.CustomEventHandler'.我尝试将EventHandler强制转换为正确的类型(使用泛型类型参数,因为这是一个外部项目的事件处理程序),但是转换会失败。

    public event EventHandler ElementEvent
    {
        add
        {
            _handlers.Add(value);
            foreach (T t in this)
            {
                _info.AddEventHandler(t, value);
            }
        }
        remove
        {
            foreach (T t in this)
            {
                _info.RemoveEventHandler(t, value);
            }
            _handlers.Remove(value);
        }
    }

解决方案2失败

我还没有找到让列表本身处理事件的好方法,然后为任何订阅者调用委托。我发现尝试使用反射来添加事件处理程序需要委托。在我的测试中,我找不到保留事件参数的方法,并将这些参数传递给订阅者

请求: 关于如何实现这一点还有其他想法吗?

2 个答案:

答案 0 :(得分:5)

编辑:

public abstract class ManagedEventCollection<T,TEventArgs> : IList<T>
{
   private EventInfo m_event;
   public ManagedEventCollection(string eventName)
   {
      m_list = new ObservableCollection<T> ();
      m_list.CollectionChanged += CollectionChanged;
      m_event = typeof (T).GetEvent (eventName);
   }
   // Add/Remove/Indexer/Clear methods alter contents of m_list.

   public EventHandler<TEventArgs> Handler{get;set;}

   protected abstract void OnItemAdded(T item);
   protected abstract void OnItemRemoved(T item);

   private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs ea)
   {
      foreach (T item in ea.NewItems)
      {
         m_event.AddEventHandler (
            item, 
            Delegate.CreateDelegate (m_event.EventHandlerType, item, Handler.Method));
      }
      foreach (T item in ea.OldItems)
      {
         m_event.RemoveEventHandler (
            item, 
            Delegate.CreateDelegate (m_event.EventHandlerType, item, Handler.Method));
      }
   }
}

原始答案: 您可以使用ObservableCollection<T>。此类有一个CollectionChanged事件,您可以根据需要订阅/取消订阅事件。

我会创建一个基类(这是来自内存,只是为了得到点)。

public abstract class ManagedEventCollection<T> : IList<T>
{
   public ManagedEventCollection()
   {
      m_list = new ObservableCollection<T> ();
      m_list.CollectionChanged += CollectionChanged;
   }
   ... // Add/Remove/Indexer/Clear methods alter contents of m_list.
   protected abstract void OnItemAdded(T item);
   protected abstract void OnItemRemoved(T item);

   private ObservableCollection<T> m_list;
   private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs ea)
   {
      foreach (T item in ea.NewItems)
         OnItemAdded(item);
      foreach (T item in ea.OldItems)
         OnItemRemoved(item);
   }
}

然后,您的派生类型可以执行此操作:

public class DogManagedEventCollection : ManagedEventCollection<Dog>
{
   protected override OnItemAdded (Dog dog)
   {
      dog.Bark += Bark;
   }
   protected override OnItemRemoved (Dog dog)
   {
      dog.Bark -= Bark;
   }

   private void Bark(object sender, BarkEventArgs ea){...}
}

如果您真的想要,也可以订阅反射,但这样会更容易出错并且不易于阅读/维护/理解。

答案 1 :(得分:1)

目前还没有测试过,所以请谨慎行事:)。

如果出于某种原因ObservableCollection不是你想要的那样(顺便说一下,我认为这是一个比我更好,更清洁的解决方案),那么这样的事情会不会起作用吗?

public class MyEventList<TElementType,TEventArgType>: IList<TElementType> where TEventArgType: EventArgs
{
    private EventInfo eventInfo;
    private EventHandler<TEventArgType> eventHandler;

    public MyEventList(string eventName, EventHandler<TEventArgType> eventHandler)
    {
        if (eventHandler == null)
            throw new ArgumentNullException("eventHandler");
        if (eventName == null)
            throw new ArgumentNullException("eventName");

        this.eventInfo = typeof(TElementType).GetEvent(eventName);

        if (this.eventInfo == null)
            throw new ArgumentException("Specified event not found.", "eventName");

        if (this.eventInfo.EventHandlerType != eventHandler.GetType())
            throw new ArgumentException("EventHandler type does not match specified event.", "eventHandler");

        this.eventHandler = eventHandler;
    }

    public void Add(TElementType item)
    {
        ...
        eventInfo.AddEventHandler(item, this.eventHandler);
        ...
    }

    public bool Remove(TElementType item)
    {
        ...
        eventInfo.RemoveEventHandler(item, this.eventHandler);
        ...
    }

    ...

 }