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

11

好的,代码结构问题:

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

在C#中是否有一种优雅的方式来处理这个问题(可能使用事件或委托)?显然,我可以添加通用的OnFruitReceived(Fruit f)事件处理程序,并使用if语句来过滤不需要的子类,但这似乎不够优雅。有没有更好的想法?谢谢!

编辑:我刚刚发现通用委托,它们似乎是一个好的解决方案。这听起来是个好主意吗?


你可以将Fruit泛型化 - Fruit<T> - Daniel A. White
@DanielA.White 我不确定我看得出来那会如何解决它。你能详细说明一下吗? - thomas88wp
我已经编辑了你的标题。请参考“问题的标题应该包含“标签”吗?”,在那里达成共识是“不应该”。 - John Saunders
谢谢@JohnSaunders - 我会记住这个建议的。 - thomas88wp
6个回答

3

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

以下是完整示例。请注意,示例相当简洁,并排除了一些典型的安全检查,如空检查。如果从 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
这似乎是一个观察者模式的问题。使用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的Unity,所以我无法访问IObservable。 - thomas88wp
@thomas88wp 很遗憾听到这个消息。请确保在将来的问题中添加此类标签,但为了未来的查询,我将在此处留下此答案。 - David
是的,我考虑在描述中包含这个(因为这是我的具体问题),但我也希望这个线程对于那些有同样问题的人有所帮助,所以你的答案可能会对其他人有用。此外,感谢上面的@Colin指出Unity支持.NET 3.5的子集和所有2.0(尽管仍然没有“IObservable”)。 - thomas88wp

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

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

嗨,Sriram,感谢你提供详细的解决方案。我认为它与我选择的那个非常相似,对于任何有兴趣的人来说都可能很有效。我喜欢另一个使用委托(Action)而不是接口类型的方法,因为我可能会有一个逻辑类处理许多水果类型,如果要实现一堆接口可能会变得有点麻烦。尽管如此,这仍然是一个有用的片段,谢谢。 - thomas88wp

0
我建议使用责任链设计模式。您可以创建一条FruitHandlers的链。一旦收到水果,它将通过此链传递,直到处理其类型的水果的处理程序能够处理为止。

0
显然,我可以添加通用的OnFruitReceived(Fruit f)事件处理程序,并使用if语句来过滤不需要的子类。
我担心你找不到其他方法,或者实际上你找不到更“简短”的方法,所以我建议你节省时间并开始输入你的if语句。

0
首先,不要使用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 {}

你可以在水果处理程序的实现中创建一个内部查找表。
Dictionary<Type, List<IFruitLogic>> fruitHandlers;

当注册新的处理程序时,您需要获取类型并将其添加到列表中。使用该列表仅调用与该类相关的处理程序。这是一个简单的示例。由于您的处理程序可能具有不同的方法,因此您也可以直接传递方法本身。
在默认情况下,您也可以只使用以下内容:
List<FruitLogic> handlers;

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

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

请注意,代码示例不一定可编译,仅供参考。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接