【问题标题】:Convert generic parameter with 'where' type constraint not possible?无法使用“where”类型约束转换泛型参数?
【发布时间】:2013-05-05 18:21:51
【问题描述】:

我有一个基类:

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&lt;T&gt; subscriber where T : DomainEvent 转换为 DomainEventSubscriber&lt;DomainEvent&gt;

eventSubscriber = (DomainEventSubscriber<DomainEvent>)subscriber;

我将如何进行这种转换,或者我是否正在为讨厌的代码气味做准备?

【问题讨论】:

  • 你需要一个带有 值的接口。不知道你是否可以在这里这样做。
  • 尽管如此,值得将out 粘贴到public abstract class DomainEventSubscriber&lt;out T&gt; where T : DomainEvent 以查看它是否有效。
  • @MatthewWatson 这将永远起作用,因为 outin 泛型类型修饰符只能用于接口,不能用于类
  • @Virtlink 很公平,也许他可以尝试将其设为接口!
  • 你的设计有缺陷。您试图让自己能够将简单的 DomainEvent 实例发送到 HandleEvent(MyDomainEvent) 方法,这将永远无法工作。编译器做得很好,可以保护您不这样做。

标签: c# generics


【解决方案1】:

协方差

您需要一个带有协变类型参数T 的接口才能将其转换为T 的基本类型。比如IEnumerable&lt;out T&gt;就是这样一个接口。注意 out 关键字,这意味着 T 是协变的,因此只能出现在 output 位置(例如,作为返回值和 getter)。由于协方差,您可以将IEnumerable&lt;Dolphin&gt; 转换为IEnumerable&lt;Mammal&gt;:可枚举的海豚序列当然也是可枚举的哺乳动物序列。

逆变

但是,您不能将DomainEventSubscriber&lt;T&gt; 设为接口IDomainEventSubscriber&lt;out T&gt;,因为T 然后出现在HandleEvent输入 位置。您可以将其设为接口IDomainEventSubscriber&lt;in T&gt;

注意in 关键字,这意味着T逆变 并且只能出现在输入 位置(例如作为方法参数)。比如IEqualityComparer&lt;in T&gt;就是这样一个接口。由于逆变,您可以将IEqualityComparer&lt;Mammal&gt; 转换为IEqualityComparer&lt;Dolphin&gt;:如果它可以比较哺乳动物,那么它肯定可以比较海豚,因为它们是哺乳动物。

但这也不能解决您的问题,因为您只能将逆变类型参数转换为 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&lt;DomainEvent&gt;

public class DomainEventPublisher
{
    private List<IDomainEventSubscriber> subscribers;

    public void Subscribe<T>(DomainEventSubscriber<T> subscriber)
        where T : DomainEvent
    {
        if (!this.Publishing)
        {
            this.subscribers.Add(eventSubscriber);
        }
    }
}

【讨论】:

    【解决方案2】:

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

    泛型基类可以被继承,但是,不同类型的参数只是不同的类。考虑List&lt;int&gt;List&lt;String&gt;。虽然它们都有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&lt;byte&gt; 的实例将在运行时无效。

    因此,即使您指定了约束,它也不起作用。

    除了现有的答案之外,另一种方法是为泛型类定义一个基类(也可以是抽象的),并让您的 Subscribe 采用基类。但是,这需要您将抽象方法移至基类并将方法签名更改为泛型方法;所以它可能不适用于您。

    要了解类型差异和分配能力,您可能想看看我的问答风格问题:

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

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

    【讨论】:

      【解决方案3】:

      下面是一个稍微不同的界面:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多