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

C# 为特定子类注册事件处理程序,c#,.net,events,.net-2.0,C#,.net,Events,.net 2.0,好的,代码结构问题: 假设我有一个类,FruitManager,它定期从某个数据源接收Fruit对象。我还有一些其他类需要在收到这些Fruit对象时得到通知。然而,每个类只对某些类型的水果感兴趣,每个水果对于如何处理它有不同的逻辑。例如,假设CitrusLogic类具有方法onFroothReceived(Orange o)和onFroothReceived(Lemon l),当收到相应的水果子类型时,应该调用这些方法,但不需要通知其他水果 有没有一种方法可以在C#中优雅地处理这个问题(可能是通

好的,代码结构问题:

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

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


编辑:我刚刚发现,它们似乎是一个很好的解决方案。这听起来像是一个好的方向吗?

这听起来像是一个问题。使用
System.Reactive.Linq
,我们还可以访问包含一系列用于观察者的Linq方法的类,包括

Subscribe(新的CitrusLogic()); Subscribe(新的LemonLogic()); ... 公共类Ciruslogic:IObersver { ... }
如果您需要按类型添加所有现有重载,例如
AFruitLogic
的所有实现,则需要使用反射扫描程序集,或者查看各种IoC方法,例如我建议采用责任链设计模式。您可以创建一个水果处理程序链。一旦收到一个水果,它就会通过这个链,直到处理程序能够处理它的水果类型。

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

至于什么是最有效的方法将水果通过处理者,这是一个更困难的问题,因为它高度依赖于你的特殊情况

我要做的是创建一个水果处理外观,它接受所有的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 {}
公共类FruitLogic,其中T:Fruit{}
您可以在水果处理程序实现中内部创建查找表

Dictionary<Type, List<IFruitLogic>> fruitHandlers;
字典处理程序;
注册新的处理程序后,将获取该类型,然后将其添加到列表中。使用该列表仅调用对该类重要的处理程序。这是一个粗略的例子。因为处理程序可能有不同的方法,所以也可以只传递方法本身

在默认情况下,您也可以

List<FruitLogic> handlers;
列表处理程序;
并让每个处理程序处理自己的过滤

重要的是要建立一个API,使它能够灵活地处理实现细节,从而使您的领域达到最佳。在现实环境中衡量不同解决方案的性能是为您找到最佳解决方案的唯一方法

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

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


我担心您找不到其他方法,或者实际上您找不到“更短”的方法,因此我建议节省您的时间并开始键入if语句。

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

下面的代码不是用.Net2.0编写的,但是您可以轻松地 通过取消使用少数Linq方法,将其修改为与.Net2.0兼容

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);
    }
}
哪个输出

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

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

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

继续你的问题,C#中的一般事件模式是使用委托和事件关键字。由于您只希望在传入的结果与其方法定义兼容时调用处理程序,因此可以使用字典来完成查找。关键在于将代理存储为什么类型。您可以使用一个小的类型魔术,使它的工作和存储的一切

Dictionary<Type, Action<Fruit>> handlers = new Dictionary<Type, Action<Fruit>>();
这将保持公共API的干净性和类型特定性。在内部,学员需要从
操作
更改为
操作
。为此,请创建一个新的委托,该委托接收
水果
,并将其转换为
T

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

你可以让水果成为通用的-
水果
@DanielA.White我不确定我能不能解决这个问题。你能详细说明一下吗?我已经编辑了你的标题。请参阅“”,其中的共识是“不,他们不应该”。谢谢@JohnSaunders-我以后会记住这一点。这是一个很好的解决方案,但不幸的是,我与.NET 2.0合作,所以我无法访问IObservable。@thomas88wp很抱歉听到这个消息。请务必在以后的问题中添加这样的标签,但为了以后的查询,我将在这里留下这个答案。是的,我考虑过在描述中包含这个标签(因为这是我的具体问题),但我也希望这个帖子对一般有相同问题的人有所帮助,所以你的答案可能会对其他人有用。还有,谢谢
Apple event fired: From AppleLogic
Lemon event fired: From CitrusLogic
Orange event fired: From CitrusLogic
Dictionary<Type, Action<Fruit>> handlers = new Dictionary<Type, Action<Fruit>>();
public void RegisterHandler<T>(Action<T> handler) where T : Fruit
Action<Fruit> wrapper = fruit => handler(fruit as T);
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);
            }
        }
    }
}