将通用参数转换为'其中'类型约束不可能吗?

时间:2013-05-05 18:21:52

标签: c# generics

我有一个基类:

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 : DomainEvent转换为DomainEventSubscriber<DomainEvent>

eventSubscriber = (DomainEventSubscriber<DomainEvent>)subscriber;

我将如何进行此转换,或者我是否为自己设置了令人讨厌的代码味道?

3 个答案:

答案 0 :(得分: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>IEqualityComparer<Dolphin>:如果它可以比较哺乳动物,那么它肯定可以比较海豚,因为它们是哺乳动物。

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


解决方案

我建议您创建一个非通用的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);
        }
    }
}

答案 1 :(得分: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成为基类。但是,这需要您将抽象方法移动到基类并将方法签名更改为通用方法;所以它可能不适合你。

要了解类型差异和分配能力,您可能需要查看我的Q&amp; A样式问题:

How to find the minimum covariant type for best fit between two types?

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

答案 2 :(得分: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);
        }
    }
}