注册事件处理特定的子类子类、事件

2023-09-03 03:19:36 作者:持宠而娇

好吧,code结构的问题:

Ok, code structure question:

让我们说我有一个类, FruitManager ,定期从一些数据源接收水果的对象。我也有得到通知说需要一些其他的类都收到了这些水果对象时。然而,每个班只关心某些类型的水果,每个水果都有不同的逻辑,应该如何处理。例如说在 CitrusLogic 类具有 OnFruitReceived(橙色O) OnFruitReceived(柠檬升),接收到的水果各亚型时,应叫,但它并不需要其他水果的通知。

Let's say I have a class, FruitManager, that periodically receives Fruit objects from some data-source. I also have some other classes that need to get notified when these Fruit objects are received. However, each class is only interested in certain types of fruit, and each fruit has different logic for how it should be handled. Say for example the CitrusLogic class has methods OnFruitReceived(Orange o) and OnFruitReceived(Lemon l), which should be called when the respective subtype of fruit is received, but it doesn't need to be notified of other fruits.

有没有办法来很好地处理这在C#(presumably与事件或代表)?很显然,我只需要添加普通的 OnFruitReceived(水果F)事件处理程序,并使用if语句来过滤不必要的子类,但是这看起来不太优雅。有没有人有一个更好的主意吗?谢谢!

Is there a way to elegantly handle this in C# (presumably with events or delegates)? Obviously I could just add generic OnFruitReceived(Fruit f) event handlers, and use if statements to filter unwanted subclasses, but this seems inelegant. Does anyone have a better idea? Thanks!

修改:我刚刚发现的泛型委托的,他们看起来他们可能是一个很好的解决方案。这听起来像一个很好的方向走?

Edit: I just found generic delegates and they seem like they could be a good solution. Does that sound like a good direction to go?

推荐答案

首先,统一支持.NET 3.5,其中特定子集取决于你的编译参数的一个子集。

First off, Unity supports a subset of .NET 3.5 where the particular subset depends on your build parameters.

移动到你的问题,在C#中的常规事件模式是使用委托和事件的关键字。既然你要处理只能被称为如果传入的水果是它的方法定义兼容,可以使用字典来完成查找。关键是要存储的代表为何种类型。您可以使用一个小的类型的魔法,使工作和储存一切,

Moving on to your question, the general event pattern in C# is to use delegates and the event keyword. Since you want handlers only to be called if the incoming fruit is compatible with its method definition, you can use a dictionary to accomplish the lookup. The trick is what type to store the delegates as. You can use a little type magic to make it work and store everything as

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

这是不理想的,因为现在所有的处理程序似乎接受水果,而不是更具体的类型。这只是内部重新presentation然而,公开的人仍然可以通过

This is not ideal, because now all the handlers seem to accept Fruit instead of the more specific types. This is only the internal representation however, publicly people will still add specific handlers via

public void RegisterHandler<T>(Action<T> handler) where T : Fruit

这使得公共API干净,然后键入特定的。在内部,委托需要从动作&LT改变; T&GT; 动作&LT;水果&GT; 。要做到这一点创建一个新的委托,它接受一个水果并将其转换成 T

This keeps the public API clean and type specific. Internally the delegate needs to change from Action<T> to Action<Fruit>. To do this create a new delegate that takes in a Fruit and transforms it into a T.

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

这当然不是一个安全的演员。它会崩溃,如果它传递任何不是 T (或 T 继承)。这就是为什么它是非常重要的是它仅在内部,而不是外部类的暴露存储。存放在此功能下的键入的typeof(T)的处理程序字典。

This is of course not a safe cast. It will crash if it is passed anything that is not T (or inherits from T). That is why it is very important it is only stored internally and not exposed outside the class. Store this function under the Type key typeof(T) in the handlers dictionary.

接下来调用事件需要自定义的功能。此功能需要调用从参数一路上扬继承链的所有类型的事件处理程序,以最普通的水果处理程序。这样一个功能,可在任何亚型参数触发为好,不只是它的特定类型。这似乎是直观的行为给我,但可以被排除在外,如果需要的。

Next to invoke the event requires a custom function. This function needs to invoke all the event handlers from the type of the argument all the way up the inheritance chain to the most generic Fruit handlers. This allows a function to be trigger on any subtype arguments as well, not just its specific type. This seems the intuitive behavior to me, but can be left out if desired.

最后,一个正常的事件可以被暴露,以允许按通常的方法被添加捕获全部水果处理程序。

Finally, a normal event can be exposed to allow catch-all Fruit handlers to be added in the usual way.

下面是完整的例子。请注意,这个例子是相当小的,不包括一些典型的安全检查,如空检查。还有一个潜在的无限循环,如果没有继承链从孩子。看到合适的实际实施应该扩大。它也可以使用一些优化。特别是在大量使用场景缓存的继承链可能是很重要的。

Below is the full example. Note that the example is fairly minimal and excludes some typical safety checks such as null checking. There is also a potential infinite loop if there is no chain of inheritance from child to parent. An actual implementation should be expanded as seen fit. It could also use a few optimizations. Particularly in high use scenarios caching the inheritance chains could be important.

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);
            }
        }
    }
}