从接口构造具体类的泛型类

时间:2017-06-05 17:39:14

标签: c# generics

我有一个WCF服务,它接受任何实现接口的实体。当它收到这些实体中的一个时,我想发布一个事件,即

public void Receive(IFruit fruit)
{
      messageHub.Publish(new FruitReceived<IFruit>(fruit));
}

但是我想重新启用界面,而不是处理订阅事件FruitReceived<IFruit>的水果的所有内容,他们只能订阅他们感兴趣的类型,例如FruitReceived<Apple>

目前我可以通过一些冗长的反思来做到这一点:

var fruitType = fruit.GetType();
var evt = typeof(FruitReceived<>)
    .MakeGenericType(fruitType)
    .GetConstructor(fruitType)
    .Invoke(fruit);

这有点受到性能影响(即使缓存了构造函数)也难以阅读。

我希望有一种更简单的方法来实现这一目标?我花了很多时间思考这个解决方案,这是我能想到的唯一一个。

作为参考,发布方法简化为以下内容:

public void Publish<TEvent>(TEvent evt)
{
    if(_subscriptions.ContainsKey(typeof(TEvent))
    {
        IEnumerable<IEventHandler<TEvent>> handlers = _subscriptions[typeof(TEvent)];

        foreach(var handler in handlers)
        {
            handler.HandleEvent(evt);
        }
    }
}

2 个答案:

答案 0 :(得分:0)

潜在的问题似乎是你正在接收IFruit的实例,但是你想在下游区分不同的具体类型。

将类强制转换为它实现的接口的好处是,消费者只需要知道声明的类型是什么。他们知道这是一个IFruit,这就是他们需要知道的全部内容。一旦他们需要了解更多,就会减少收益。

换句话说,如果您完全关心AppleOrange之间的差异,那么为什么要将其投放为IFruit?当然,实现之间存在差异,但这些差异 - 即使存在不同的实现 - 应该对依赖IFruit的任何内容都是透明的。

没有完美的方法来处理这个问题。如果您没有创建泛型类型(如在帖子中),那么您正在执行此操作:

if(fruit is Apple)

无论如何都会有类型创建或类型检查。

你可以解决问题。有一个处理FruitReceived<IFruit>的事件处理程序。然后,该事件处理程序创建更具体的事件类型并重新引发它,以便更具体的事件处理程序可以捕获它。这样,您就可以拥有特定于AppleOrange等的事件处理程序

它并不完美,但它将问题从事件发生的地方转移到另一个有助于提升更具体事件类型的类。

这有益的另一个原因是您的设计允许多个事件处理程序。所以可以想象,你可以在具体类型是FruitEvent<IFruit>的地方引发Apple,所以你想要一个特定于苹果的事件处理程序,但你也想要一个通用的IFruit事件处理程序来执行。如果您在提升之前将事件转换为FruitEvent<Apple>,那么您将不会执行通用事件处理程序。

答案 1 :(得分:0)

这通常可以通过访客模式来解决。但它需要进行一些广泛的更改,从IFruit开始:

interface IFruitVisitor {
    void Visit(Apple apple);
    void Visit(Banana banana);
    // ... you need a method for each fruit
}

interface IFruit {
    Accept(IFruitVisitor visitor);
}

然后你的成果必须实现这个方法:

class Apple : IFruit {
    public void Accept(IFruitVisitor visitor) => visitor.Visit(this);
}
class Banana : IFruit {
    public void Accept(IFruitVisitor visitor) => visitor.Visit(this);
}

你可以为你的案件设立一个特殊的访问者:

class CreateFruitReceivedFruitVisitor : IFruitVisitor {
    public object FruitReceived { get; private set; }
    public void Visit(Banana banana) => FruitReceived = new FruitReceived<Banana>(banana);
    public void Visit(Apple apple) => FruitReceived = new FruitReceived<Apple>(apple);
}

然后,只需在原始方法中使用它:

public void Receive(IFruit fruit)
{
    var visitor = new CreateFruitReceivedFruitVisitor();
    fruit.Accept(visitor);
    messageHub.Publish(visitor.FruitReceived);
}

您需要考虑此解决方案的优势和成本。即使它比你展示的反射版本更快,但我相信它更加笨拙。