【问题标题】:Are there any rules for type conversion between C# generics?C#泛型之间的类型转换是否有任何规则?
【发布时间】:2016-03-16 14:18:21
【问题描述】:

我有一个接口 IJob,所有 Job 类都从该接口继承,并且我有一个通用的 repo 类,它将处理所有类型的 IJob。

我面临的问题是我无法将Repository<Job1> 转换为Repository<IJob>,即使Job1IJob 的一种类型。

代码

internal class Program
{
    public interface IJob { }
    public class Job1 : IJob { }
    public class Job2 : IJob { }

    public class Repository<TJob>
        where TJob : IJob
    {
        public List<TJob> GetJobs()
        {
            return new List<TJob>();
        }
    }

    private static void Main(string[] args)
    {
        IJob iJob = new Job1(); // Valid
        Repository<IJob> repo = new Repository<Job1>(); // Not Valid
    }
}

有人可以告诉我为什么这在 C# 中不可能吗?在 C# 泛型中还有其他规则类型转换吗?

【问题讨论】:

  • 您正在寻找通用方差...但在这种情况下,您将无法使您的类协变,因为类不支持方差。见msdn.microsoft.com/en-us/library/ms228359(v=vs.90).aspx
  • @JonSkeet 当然,让我看看
  • 你必须做一些事情,比如让 Repository 显式实现一个非泛型 IRepository 接口,它提供对 IJob 集合的相同类型的访问,就像 Repository 对具体 Job 类的集合所做的那样。
  • 这里每天都会问这个问题。还有一次:一盒橙子不是一盒水果。为什么不?因为你可以把一个苹果放进一盒水果里,但不能放进一盒橘子里。一盒水果不是一盒橙子。为什么?因为一盒水果可能包含一个苹果。结论:一盒水果和一盒橙子之间没有类型关系,不管水果和橙子之间是否存在关系。

标签: c# generics


【解决方案1】:

如果您想要协方差,那么您必须为存储库引入一个通用接口并将GetJobs 方法的返回类型更改为IEnumerable&lt;TJob&gt;

internal class Program
{
    public interface IJob { }
    public class Job1 : IJob { }
    public class Job2 : IJob { }

    public interface IRepository<out TJob> where TJob : IJob
    {
        IEnumerable<TJob> GetJobs();
    }

    public class Repository<TJob> : IRepository<TJob> where TJob : IJob
    {
        public IEnumerable<TJob> GetJobs()
        {
            return new List<TJob>();
        }
    }

    private static void Main(string[] args)
    {
        IJob iJob = new Job1(); // Valid
        IRepository<IJob> repo = new Repository<Job1>(); // Also valid
    }
}

有关协变和逆变的更多信息,请参阅:https://msdn.microsoft.com/en-us/library/dd799517%28v=vs.110%29.aspx

【讨论】:

    【解决方案2】:
    Repository<IJob> repo = new Repository<Job1>(); // Not Valid
    

    是的,因为它不是Repository&lt;IJob&gt;。使用Repository&lt;IJob&gt;,此行将有效:

    repo.GetJobs().Add(new Job2());
    

    现在,Repository&lt;Job1&gt;,此行无效。它不会编译。

    所以你得到的错误是正确的。 Repository&lt;Job1&gt;不是Repository&lt;IJob&gt;

    【讨论】:

      【解决方案3】:

      你只是想要

      Repository<IJob> repo = new Repository<IJob>();
      

      然后您可以将任何IJob 添加到存储库。

      repo.Add(new Job1());
      repo.Add(new Job2());
      

      【讨论】:

      • 技术上应该是repo.GetJobs().Add( 而不是repo.Add(
      • @ScottChamberlain 是的。目前实现不是很好 - 将项目添加到新列表不会让您获得太多。
      【解决方案4】:

      要理解为什么不能这样做,我们需要回顾一下在 c# 中成为对象的子类意味着什么。当你遇到这样的情况时:

      public class A 
      {
          public A GetInstance { get;set;}
      }
      
      public class B : A
      {
      
      }
      

      类 B 继承了类 A 的所有方法,包括方法的签名。事实上,你可以创建一个 B 的实例,它有一个 GetInstance 方法 ThatReturns A,但没有 B。如果你试图重写 GetInstance 方法来返回 B,它当然会给出一个编译错误(如果一个方法不能被 ovveridden唯一的变化是返回类型)。

      使用泛型有点不同,因为更改输入中的类型会更改所有方法的签名。

      简单地说,即使 Job1 是 IJob 的子类,Repository(IJob) 类的方法与 Repository(Job1) 类的方法具有不同的签名,因此它们不相关,但就像两个独立的类。

      【讨论】:

        猜你喜欢
        • 2013-03-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-17
        • 2022-08-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多