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

    // ... 
}