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

时间:2015-05-21 15:49:14

标签: c# .net events .net-2.0

好的,代码结构问题:

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

有没有办法在C#中优雅地处理这个问题(可能是事件或代表)?显然我可以添加泛型OnFruitReceived(Fruit f)事件处理程序,并使用if语句来过滤不需要的子类,但这看起来不太优雅。有没有人有更好的主意?谢谢!

修改:我刚刚找到了generic delegates,他们似乎认为这是一个很好的解决方案。这听起来像是一个好方向吗?

6 个答案:

答案 0 :(得分:3)

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

继续讨论您的问题,C#中的常规事件模式是使用委托和事件关键字。由于只有在传入的水果与其方法定义兼容时才需要调用处理程序,因此可以使用字典来完成查找。诀窍是将代理存储为什么类型。您可以使用一个小型魔术来使其工作并将所有内容存储为

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处理程序。

以下是完整示例。请注意,该示例相当小,并且排除了一些典型的安全检查,例如空检查。如果没有从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);
            }
        }
    }
}

答案 1 :(得分:1)

这听起来像Observer pattern的问题。使用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

答案 2 :(得分:1)

我一直在使用通用事件聚合器,它可以帮助你。

以下代码不是用.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);
    }
}

您的模型或您要发布的任何内容

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

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

我要做的是创建一个Fruit处理外观,它采用所有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,使其可以灵活地使用最适合您的域的实现细节。在现实环境中测量不同解决方案的性能是为您找到最佳解决方案的唯一方法。

  

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

答案 5 :(得分:0)

  

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

我担心你不会找到另一种方式,或者实际上你找不到更短的&#39;因此,我建议您节省时间并开始输入if语句。

相关问题