7

我有一个基类:

public abstract class DomainEventSubscriber<T> where T : DomainEvent
{
    public abstract void HandleEvent(T domainEvent);
    public Type SubscribedToEventType() { return typeof(T); }
}

还有一个存储DomainEventSubscriber引用的类:

public class DomainEventPublisher
{
    private List<DomainEventSubscriber<DomainEvent>> subscribers;

    public void Subscribe<T>(DomainEventSubscriber<T> subscriber)
        where T : DomainEvent
    {
        DomainEventSubscriber<DomainEvent> eventSubscriber;
        eventSubscriber = (DomainEventSubscriber<DomainEvent>)subscriber;

        if (!this.Publishing)
        {
            this.subscribers.Add(eventSubscriber);
        }
    }
}

即使Subscribe方法类型受到限制,我也无法从转换DomainEventSubscriber<T> subscriber where T : DomainEventDomainEventSubscriber<DomainEvent>

eventSubscriber = (DomainEventSubscriber<DomainEvent>)subscriber;

我将如何进行这种转换,或者我是否要为讨厌的代码气味做好准备?

4

3 回答 3

5

协方差

您需要一个带有协变类型参数的接口T才能将其转换为T. 比如IEnumerable<out T>就是这样一个接口。注意out关键字,这意味着T是协变的,因此只能出现在输出位置(例如作为返回值和getter)。由于协方差,您可以IEnumerable<Dolphin>转换为IEnumerable<Mammal>:可枚举的海豚序列肯定也是可枚举的哺乳动物序列。

逆变

但是,您不能制作DomainEventSubscriber<T>一个界面IDomainEventSubscriber<out T>T然后出现在 的输入位置HandleEvent。你可以把它变成一个界面IDomainEventSubscriber<in T>

注意in关键字,这意味着T逆变的,并且只能出现在输入位置(例如作为方法参数)。比如IEqualityComparer<in T>就是这样一个接口。由于逆变,你可以投一个IEqualityComparer<Mammal>to IEqualityComparer<Dolphin>:如果它可以比较哺乳动物,那么它肯定可以比较海豚,因为它们是哺乳动物。

但这也不能解决您的问题,因为您只能将逆变类型参数转换为派生的类型,并且您希望将其转换为基类型。


解决方案

我建议您创建一个非泛型IDomainEventSubscriber接口并从中派生当前类:

public interface IDomainEventSubscriber
{
    void HandleEvent(DomainEvent domainEvent);
    Type SubscribedToEventType();
}

public abstract class DomainEventSubscriber<T> : IDomainEventSubscriber
    where T : DomainEvent
{
    void IDomainEventSubscriber.HandleEvent(DomainEvent domainEvent)
    {
        if (domainEvent.GetType() != SubscribedToEventType())
            throw new ArgumentException("domainEvent");

        HandleEvent((T)domainEvent);
    }

    public abstract void HandleEvent(T domainEvent);

    public Type SubscribedToEventType() { return typeof(T); }
}

然后在IDomainEventSubscriber内部使用而不是DomainEventSubscriber<DomainEvent>

public class DomainEventPublisher
{
    private List<IDomainEventSubscriber> subscribers;

    public void Subscribe<T>(DomainEventSubscriber<T> subscriber)
        where T : DomainEvent
    {
        if (!this.Publishing)
        {
            this.subscribers.Add(eventSubscriber);
        }
    }
}
于 2013-05-05T18:35:36.723 回答
3

解决方案有很好的答案,在这里我要指出一点,原因很简单,原因很简单,为什么你不能让它工作。

泛型基类可以被继承,但是,不同类型的参数只是不同的类。考虑List<int>List<String>。虽然它们都有 的泛型定义List`1,但它们采用不同的类型参数;它们都不是另一个的基类。您的方法的作用如下:

public void Subscribe<T>(List<T> subscriber) where T: struct {
    var eventSubscriber=(List<int>)subscriber;

    // ... 
}

这不会编译,甚至您将可编译的声明更改为:

public void Subscribe<T>(List<T> subscriber) where T: struct {
    var eventSubscriber=(List<int>)(subscriber as object);

    // ... 
}

并在运行时传递一个实例List<byte>将是无效的强制转换。

因此,即使您指定了约束,它也不起作用。

除了现有的答案之外,另一种方法是为泛型类定义一个基类(也可以是抽象的),并让您Subscribe采用基类。但是,这需要您将抽象方法移至基类并将方法签名更改为泛型方法;所以它可能不适用于您。

为了了解类型差异和分配能力,您可能想看看我的问答风格问题:

如何找到两种类型之间最佳拟合的最小协变类型?

您可以使用它来获取有关类型的一些信息,以进行调试,我希望这对您了解类型有所帮助。

于 2013-05-05T23:39:37.973 回答
1

这是使用界面的一个稍微不同的看法:

public class DomainEvent
{
}

// The 'in' isn't actually needed to make this work, but it can be added anyway:

public interface IDomainEventSubscriber<in T> where T: DomainEvent
{
    void HandleEvent(T domainEvent);
    Type SubscribedToEventType();
}

public abstract class DomainEventSubscriber<T>: IDomainEventSubscriber<T> where T: DomainEvent
{
    public abstract void HandleEvent(T domainEvent);
    public Type SubscribedToEventType()
    {
        return typeof(T);
    }
}

public class DomainEventPublisher
{
    private List<DomainEventSubscriber<DomainEvent>> subscribers;

    public void Subscribe<T>(IDomainEventSubscriber<T> subscriber)
        where T: DomainEvent
    {
        DomainEventSubscriber<DomainEvent> eventSubscriber;
        eventSubscriber = (DomainEventSubscriber<DomainEvent>)subscriber;

        if (!this.Publishing)
        {
            this.subscribers.Add(eventSubscriber);
        }
    }
}
于 2013-05-05T18:39:12.810 回答