为特定子类注册事件处理程序

本文关键字:事件处理 程序 注册 子类 | 更新日期: 2023-09-27 17:50:14

好的,代码结构问题:

假设我有一个类FruitManager,它定期从某个数据源接收Fruit对象。当这些Fruit对象被接收时,我也有一些其他类需要得到通知。然而,每个类只对特定类型的水果感兴趣,并且每个水果都有不同的逻辑来处理它。例如,CitrusLogic类有OnFruitReceived(Orange o)OnFruitReceived(Lemon l)方法,它们应该在接收到相应的水果子类型时调用,但不需要通知其他水果。

是否有一种方法可以在c#中优雅地处理这个问题(大概是用事件或委托)?显然,我可以只添加通用的OnFruitReceived(Fruit f)事件处理程序,并使用if语句来过滤不需要的子类,但这似乎不太优雅。有人有更好的主意吗?谢谢!

编辑:我刚刚发现通用委托,他们似乎可以是一个很好的解决方案。这听起来是个好方向吗?

为特定子类注册事件处理程序

首先,Unity支持。net 3.5的子集,其中特定的子集取决于您的构建参数。

继续你的问题,c#中的一般事件模式是使用委托和event关键字。由于您希望只在传入水果与其方法定义兼容时调用处理程序,因此可以使用字典来完成查找。诀窍在于将委托存储为何种类型。您可以使用一些类型魔术来使它工作并将所有内容存储为

Dictionary<Type, Action<Fruit>> handlers = new Dictionary<Type, Action<Fruit>>();

这不是理想的,因为现在所有的处理程序似乎都接受Fruit而不是更具体的类型。这只是内部表示,然而,公开的人们仍然会通过

添加特定的处理程序。
public void RegisterHandler<T>(Action<T> handler) where T : Fruit

这使公共API保持干净和类型特定。内部委派需要从Action<T>更改为Action<Fruit>。要做到这一点,创建一个新的委托,接受Fruit并将其转换为T

Action<Fruit> wrapper = fruit => handler(fruit as T);

这当然不是一个安全的类型转换。如果传递的不是T(或继承自T),它将崩溃。这就是为什么它只存储在内部而不暴露在类外部是非常重要的。将此函数存储在处理程序字典中的Typetypeof(T)下。

接下来调用事件需要一个自定义函数。这个函数需要调用所有事件处理程序,从参数的类型一直到继承链上最通用的Fruit处理程序。这允许在任何子类型参数上触发函数,而不仅仅是它的特定类型。这似乎是我的直觉行为,但如果需要,可以忽略。

最后,可以公开一个普通事件,以允许以常规方式添加捕获所有Fruit处理程序。

下面是完整的示例。请注意,这个示例相当小,并且排除了一些典型的安全检查,例如null检查。如果没有从childparent的继承链,也可能出现无限循环。实际的实施应该在认为合适的情况下进行扩展。它还可以使用一些优化。特别是在高使用场景中,缓存继承链可能很重要。

public class Fruit { }
class FruitHandlers
{
    private Dictionary<Type, Action<Fruit>> handlers = new Dictionary<Type, Action<Fruit>>();
    public event Action<Fruit> FruitAdded
    {
        add
        {
            handlers[typeof(Fruit)] += value;
        }
        remove
        {
            handlers[typeof(Fruit)] -= value;
        }
    }
    public FruitHandlers()
    {
        handlers = new Dictionary<Type, Action<Fruit>>();
        handlers.Add(typeof(Fruit), null);
    }
    static IEnumerable<Type> GetInheritanceChain(Type child, Type parent)
    {
        for (Type type = child; type != parent; type = type.BaseType)
        {
            yield return type;
        }
        yield return parent;
    }
    public void RegisterHandler<T>(Action<T> handler) where T : Fruit
    {
        Type type = typeof(T);
        Action<Fruit> wrapper = fruit => handler(fruit as T);
        if (handlers.ContainsKey(type))
        {
            handlers[type] += wrapper;
        }
        else
        {
            handlers.Add(type, wrapper);
        }
    }
    private void InvokeFruitAdded(Fruit fruit)
    {
        foreach (var type in GetInheritanceChain(fruit.GetType(), typeof(Fruit)))
        {
            if (handlers.ContainsKey(type) && handlers[type] != null)
            {
                handlers[type].Invoke(fruit);
            }
        }
    }
}

这听起来像是Observer模式的问题。使用System.Reactive.Linq,我们还可以访问Observable类,其中包含一系列用于观察者的Linq方法,包括.OfType<>

fruitSource.OfType<CitrusFruit>.Subscribe(new CitrusLogic());
fruitSource.OfType<LemonFruit>.Subscribe(new LemonLogic());
...
public class Ciruslogic : IObersver<CitrusFruit>
{ ... }

如果您需要按类型添加所有现有的重载,例如AFruitLogic<TFruit>的所有实现,则需要使用反射扫描程序集或查看各种IoC方法,例如MEF

我一直在使用通用的事件聚合器,它可以在这里帮助您。

下面的代码不是用。net 2.0编写的,但是你可以很容易地修改它以使其与。net 2.0兼容,取消使用一些Linq方法。

namespace Eventing
{
    public class EventAggregator : IEventAggregator
    {
        private readonly Dictionary<Type, List<WeakReference>> eventSubscriberLists =
            new Dictionary<Type, List<WeakReference>>();
        private readonly object padLock = new object();
        public void Subscribe(object subscriber)
        {
            Type type = subscriber.GetType();
            var subscriberTypes = GetSubscriberInterfaces(type)
                .ToArray();
            if (!subscriberTypes.Any())
            {
                throw new ArgumentException("subscriber doesn't implement ISubscriber<>");
            }
            lock (padLock)
            {
                var weakReference = new WeakReference(subscriber);
                foreach (var subscriberType in subscriberTypes)
                {
                    var subscribers = GetSubscribers(subscriberType);
                    subscribers.Add(weakReference);
                }
            }
        }
        public void Unsubscribe(object subscriber)
        {
            Type type = subscriber.GetType();
            var subscriberTypes = GetSubscriberInterfaces(type);
            lock (padLock)
            {
                foreach (var subscriberType in subscriberTypes)
                {
                    var subscribers = GetSubscribers(subscriberType);
                    subscribers.RemoveAll(x => x.IsAlive && object.ReferenceEquals(x.Target, subscriber));
                }
            }
        }
        public void Publish<TEvent>(TEvent eventToPublish)
        {
            var subscriberType = typeof(ISubscriber<>).MakeGenericType(typeof(TEvent));
            var subscribers = GetSubscribers(subscriberType);
            List<WeakReference> subscribersToRemove = new List<WeakReference>();
            WeakReference[] subscribersArray;
            lock (padLock)
            {
                subscribersArray = subscribers.ToArray();
            }
            foreach (var weakSubscriber in subscribersArray)
            {
                ISubscriber<TEvent> subscriber = (ISubscriber<TEvent>)weakSubscriber.Target;
                if (subscriber != null)
                {
                    subscriber.OnEvent(eventToPublish);
                }
                else
                {
                    subscribersToRemove.Add(weakSubscriber);
                }
            }
            if (subscribersToRemove.Any())
            {
                lock (padLock)
                {
                    foreach (var remove in subscribersToRemove)
                        subscribers.Remove(remove);
                }
            }
        }
        private List<WeakReference> GetSubscribers(Type subscriberType)
        {
            List<WeakReference> subscribers;
            lock (padLock)
            {
                var found = eventSubscriberLists.TryGetValue(subscriberType, out subscribers);
                if (!found)
                {
                    subscribers = new List<WeakReference>();
                    eventSubscriberLists.Add(subscriberType, subscribers);
                }
            }
            return subscribers;
        }
        private IEnumerable<Type> GetSubscriberInterfaces(Type subscriberType)
        {
            return subscriberType
                .GetInterfaces()
                .Where(i => i.IsGenericType &&
                    i.GetGenericTypeDefinition() == typeof(ISubscriber<>));
        }
    }
    public interface IEventAggregator
    {
        void Subscribe(object subscriber);
        void Unsubscribe(object subscriber);
        void Publish<TEvent>(TEvent eventToPublish);
    }
    public interface ISubscriber<in T>
    {
        void OnEvent(T e);
    }
}

你的模型或任何你想发布的

public class Fruit
{
}
class Orange : Fruit
{
}
class Apple : Fruit
{
}
class Lemon : Fruit
{
}
//Class which handles citrus events
class CitrusLogic : ISubscriber<Orange>, ISubscriber<Lemon>
{
    void ISubscriber<Orange>.OnEvent(Orange e)
    {
        Console.WriteLine(string.Format("Orange event fired: From {0}", this.GetType().Name));
    }
    void ISubscriber<Lemon>.OnEvent(Lemon e)
    {
        Console.WriteLine(string.Format("Lemon event fired: From {0}", this.GetType().Name));
    }
}
//Class which handles Apple events
class AppleLogic : ISubscriber<Apple>
{
    void ISubscriber<Apple>.OnEvent(Apple e)
    {
        Console.WriteLine(string.Format("Apple event fired: From {0}", this.GetType().Name));
    }
}

然后按如下方式使用

void Main()
{
    EventAggregator aggregator = new EventAggregator();
    CitrusLogic cl =new CitrusLogic();
    AppleLogic al =new AppleLogic();
    aggregator.Subscribe(cl);
    aggregator.Subscribe(al);
    //...
    aggregator.Publish(new Apple());
    aggregator.Publish(new Lemon());
    aggregator.Publish(new Orange());
}

输出
Apple event fired: From AppleLogic
Lemon event fired: From CitrusLogic
Orange event fired: From CitrusLogic

注意:上面提供的事件聚合器版本使用弱事件模式,因此您必须需要一个对订阅者的强引用来保持它的活动。如果您希望它是强引用,您可以简单地将弱引用转换为强引用。

我建议责任链设计模式。你可以创建一个fruithandler链。一旦接收到水果,它就会通过这个链,直到处理程序能够处理它的水果类型。

首先,不要使用if语句来路由逻辑。如果最终使用通用处理程序,请将所有水果传递给所有处理程序,并让处理程序进行过滤。从长远来看,这将为您节省维护的痛苦。

至于让水果通过处理程序的最有效方法是什么,这是一个更困难的问题,因为它高度依赖于您的特定情况。

我要做的是创建一个Fruit处理facade,它接受所有XLogic类,并具有某种类型的注册方法,如

IFruitHandlers fruitHandlers;
fruitHandlers.Register(new CitrusLogic()) // Or some good DI way of doing this
// later
fruitHandlers.Handle(fruit);

然后在内部,您可以处理不同的实现,看看哪些是有效的。例如,给定逻辑处理程序定义如下:

public class FruitLogic<T> where T:Fruit {}

您可以在水果处理程序实现中内部创建查找表

Dictionary<Type, List<IFruitLogic>> fruitHandlers;

当注册一个新的处理程序时,您将获取该类型,然后将其添加到列表中。使用列表只调用对该类重要的处理程序。这是一个粗略的例子。因为你的处理程序可能有不同的方法,你也可以只传递方法本身。

在默认情况下你也可以只写

List<FruitLogic> handlers;

并让每个处理程序负责自己的过滤。

重要的是建立一个API,使其能够灵活地处理实现细节,以优化您的领域。在现实环境中衡量不同解决方案的性能是找到最适合您的解决方案的唯一方法。

请注意,代码示例不一定是可编译的,只是示例。

显然我可以添加通用的OnFruitReceived(Fruit f)事件处理程序,并使用if语句来过滤不需要的子类

我担心你找不到其他方法,或者实际上你找不到更短的方法,所以我建议你节省时间,开始输入你的if语句。