C# 使用'转换通用参数;其中';类型约束不可能?
我有一个基本类:C# 使用'转换通用参数;其中';类型约束不可能?,c#,generics,C#,Generics,我有一个基本类: public abstract class DomainEventSubscriber<T> where T : DomainEvent { public abstract void HandleEvent(T domainEvent); public Type SubscribedToEventType() { return typeof(T); } } 即使Subscribe方法类型受到约束,我也无法从DomainEventSubscriber
public abstract class DomainEventSubscriber<T> where T : DomainEvent
{
public abstract void HandleEvent(T domainEvent);
public Type SubscribedToEventType() { return typeof(T); }
}
即使Subscribe
方法类型受到约束,我也无法从DomainEventSubscriber
其中T:DomainEvent
转换为DomainEventSubscriber
:
eventSubscriber=(DomainEventSubscriber)订户;
我将如何执行此转换,或者我正在为自己设置一种讨厌的代码气味?协方差
您需要一个具有协变类型参数T
的接口,才能将其转换为T
的基本类型。例如,IEnumerable
就是这样一个接口。注意out
关键字,这意味着T
是协变的,因此只能出现在输出位置(例如作为返回值和getter)。由于协方差,您可以将IEnumerable
转换为IEnumerable
:海豚的可枚举序列肯定也是哺乳动物的可枚举序列
逆变
但是,您不能将DomainEventSubscriber
作为接口IDomainEventSubscriber
,因为T
会出现在HandleEvent
的输入位置。您可以将其设置为接口IDomainEventSubscriber
注意关键字中的,这意味着T
是逆变的,只能出现在输入位置(例如作为方法参数)。例如,IEqualityComparer
就是这样一个接口。由于相反,您可以将IEqualityComparer
投射到IEqualityComparer
:如果它可以比较哺乳动物,那么它肯定可以比较海豚,因为它们是哺乳动物
但这也不能解决您的问题,因为您只能将逆变类型参数强制转换为更派生的类型,并且希望将其强制转换为基类型
解决方案
我建议您创建一个非通用的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); }
}
下面是使用界面的一个稍微不同的用法:
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);
}
}
}
公共类DomainEvent
{
}
//实际上,不需要“in”来实现此功能,但可以添加它:
公共接口IDomainEventSubscriber,其中T:DomainEvent
{
无效HandleEvent(T域事件);
类型SubscribedToEventType();
}
公共抽象类DomainEventSubscriber:IDomainEventSubscriber,其中T:DomainEvent
{
公共摘要无效HandleEvent(T domainEvent);
公共类型SubscribedToEventType()
{
返回类型(T);
}
}
公共类DomainEventPublisher
{
私人名单订户;
公共无效订阅(IDomainEventSubscriber)
其中T:DomainEvent
{
DomainEventSubscriber eventSubscriber;
eventSubscriber=(DomainEventSubscriber)订阅服务器;
如果(!this.Publishing)
{
this.subscribers.Add(eventSubscriber);
}
}
}
解决方案有很好的答案,这里我要指出一点,原因很简单,为什么你不能让它工作
泛型基类可以被继承,但是,不同类型的参数只是不同的类。考虑<代码>列表< /代码>和<代码>列表< /代码>。虽然它们都有List`1
的一般定义,但它们采用不同的类型参数;它们都不是另一个的基类。您的方法的作用如下所示:
public void Subscribe<T>(List<T> subscriber) where T: struct {
var eventSubscriber=(List<int>)subscriber;
// ...
}
public void Subscribe(列表订户),其中T:struct{
var eventSubscriber=(列表)订户;
// ...
}
这不会编译,即使您将可编译的声明更改为:
public void Subscribe<T>(List<T> subscriber) where T: struct {
var eventSubscriber=(List<int>)(subscriber as object);
// ...
}
public void Subscribe(列表订户),其中T:struct{
var eventSubscriber=(列表)(订阅服务器作为对象);
// ...
}
并传递List
的实例,则在运行时强制转换无效
因此,即使您指定了约束,它也不起作用
除了现有答案之外,另一种方法是为泛型类定义基类(也可以是抽象的),并让您的Subscribe
获取基类。但是,这需要将抽象方法移动到基类,并将方法签名更改为泛型方法;所以它可能不适用于你
为了了解类型差异和分配能力,您可能想看看我的问答式问题:
您可以使用它来获取有关类型的一些信息,以便进行调试,我希望这将有助于您理解类型 您需要一个具有值的接口。不确定您是否可以在此处执行此操作。不过,值得将out
粘贴到公共抽象类DomainEventSubscriber中,其中T:DomainEvent
以查看它是否有效。@MatthewWatson,由于out
和in
泛型类型修饰符只能用于接口,不是类。@Virtlink很公平,也许他可以试着把它变成一个接口!你的设计有缺陷。您正在尝试授权自己能够将简单的DomainEvent
实例发送到HandleEvent(MyDomainEvent)
方法,这将永远不会起作用。编译器通过保护您不这样做做做了很好的工作。
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);
}
}
}
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);
// ...
}