用'where'不可能有类型约束

本文关键字:类型 约束 可能有 where 不可能 | 更新日期: 2023-09-27 18:12:07

有一个基类:

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;

我该如何执行这个转换,或者我是否在为自己设置一个令人讨厌的代码气味?

用'where'不可能有类型约束

协方差

您需要一个带有协变类型参数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);
        }
    }
}

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

泛型基类可以被继承,但是,具有不同类型参数的基类只是不同的类。考虑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& a风格问题:

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

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

这里有一个稍微不同的使用界面:

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