用'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;
我该如何执行这个转换,或者我是否在为自己设置一个令人讨厌的代码气味?
协方差
您需要一个带有协变类型参数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);
}
}
}