【问题标题】:Contravariant Value Types逆变值类型
【发布时间】:2013-08-07 11:55:07
【问题描述】:

我已经为我的存储库创建了这个接口。

public interface IRepository<T, in TKey> where T: class
{
    IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
    IEnumerable<T> FindAll();
    T FindSingle(TKey id);
    void Create(T entity);
    void Delete(T entity);
    void Update(T entity);
}

FindSingle 方法接受一个 ID,该 ID 将用于搜索主键。通过使用in,我希望只允许将引用类型作为TKey 传递。出于好奇,我决定创建一个具体类并将其指定为 int,这样我就可以看到异常。

我查了MSDN,它指出这不应该工作

引用类型支持泛型类型参数中的协变和逆变,但值类型不支持它们。

我创建的类是这样的

public class ProjectRepository : IRepository<Project,int>
{
    public IEnumerable<Project> Find(Expression<Func<Project, bool>> predicate)
    {
        throw new NotImplementedException();
    }

    public IEnumerable<Project> FindAll()
    {
        throw new NotImplementedException();
    }

    public Project FindSingle(int id)
    {
        throw new NotImplementedException();
    }

    public void Create(Project entity)
    {
        throw new NotImplementedException();
    }

    public void Delete(Project entity)
    {
        throw new NotImplementedException();
    }

    public void Update(Project entity)
    {
        throw new NotImplementedException();
    }
}

为什么我在将TKey 指定为值类型的构建时没有收到异常?另外,如果我从参数中删除了in,我会丢失什么? MSDN 文档说逆变允许使用派生较少的类型,但通过删除 in 我可以传递任何类型,因为它仍然是通用的。

这可能表明对逆变和协变缺乏了解,但它让我有点困惑。

【问题讨论】:

  • 我怀疑编译器没有抱怨,因为intsealed,因此永远不会被用于共同或相反的变体使用。 Variance 是关于当 variant 类型的派生被替换为基类型时编译器如何处理泛型类型类的用法。例如。 List&lt;Animal&gt; animals = new List&lt;Cat&gt;(); 是协变的。而((IRepository&lt;Cat&gt;)repo).Update(animal); 是逆变的。
  • @KeithPayne:List&lt;T&gt; 是一个类。类不支持协变和逆变,因此该代码无法编译。赋值的目标需要是协变接口。
  • @DanielHilgarth 谢谢丹尼尔。这个例子应该是IEnumerable&lt;Animal&gt; animals = new List&lt;Cat&gt;(); 而第二个例子也不是很好。 ((IUpdateOnlyRepository&lt;Cat&gt;)repo).Update(animal); 更好,因为使用普通的旧存储库意味着方法也会返回变体类型。
  • @KeithPayne:正确。

标签: c# .net contravariance


【解决方案1】:

Covariance and contravariance 对值类型没有多大意义,因为它们都是密封的。虽然从文档中不清楚,但使用 struct 作为协/逆变类型是有效的,但它并不总是有用。您引用的文档很可能是指以下内容无效:

public struct MyStruct<in T>

逆变意味着您可以执行以下示例:

IRepository<string, Base> b = //something
IRepository<string, Derived> d = b;

由于int 没有派生任何内容,您可以使用IRepository&lt;string, int&gt;,但只能作为IRepository&lt;string, int&gt;

协方差意味着你可以做相反的事情,例如IEnumerable&lt;T&gt;out T,这是协变的。您可以执行以下操作:

IEnumerable<Derived> d = //something
IEnumerable<Base> b = d;

如果您尝试将 TKeyT 都限制为 classes(引用类型),则应包括第二个限制:

public interface IRepository<T, in TKey>
    where T : class
    where TKey : class

【讨论】:

    【解决方案2】:

    确实,您错过了协变和逆变的全部要点 :-) 这是关于能够将泛型类型的变量分配给相同泛型类型但具有不同泛型类型参数的另一个变量与源中使用的相关。
    根据泛型类型参数是协变还是逆变,允许进行不同的赋值。

    假设如下界面:

    public interface IRepository<in T>
    {
        void Save(T value);
    }
    

    此外,假设以下接口以及实现它的值类型和引用类型:

    public interface IBar
    {
    }
    
    public struct BarValueType : IBar
    {
    }
    
    public class BarReferenceType : IBar
    {
    }
    

    最后,假设两个变量:

    IRepository<BarReferenceType> referenceTypeRepository;
    IRepository<BarValueType> valueTypeRepository;
    

    现在,逆变意味着您可以将IRepository&lt;IBar&gt; 的实例分配给变量referenceTypeRepository,因为BarReferenceType 实现了IBar
    您引用的 MSDN 中的部分仅表示将 IRepository&lt;IBar&gt; 的实例分配给 valueTypeRepository 是不合法的,尽管 BarValueType 也实现了 IBar

    【讨论】:

      【解决方案3】:

      用值类型实现你的接口没有问题。例如,在尝试将 IRepository&lt;Project, object&gt; 分配给 IRepository&lt;Project, int&gt; 时,您只会收到错误消息。在下面的代码中,最后的赋值不会编译:

      public interface IContravariant<T, in TKey> where T : class
      {
          T FindSingle(TKey id);
      }
      public class objCV : IContravariant<Project, object>
      {
          public Project FindSingle(object id)
          {
              return null;
          }
          public static void test()
          {
              objCV objcv = new objCV();
      
              IContravariant<Project, Project> projcv;
              IContravariant<Project, int> intcv;
      
              projcv = objcv;
              intcv = objcv;
          }
      }
      

      【讨论】:

        【解决方案4】:

        在这篇文章中,他们告诉我们类型参数被编译器视为不变

        方差仅适用于引用类型;如果您指定值类型 对于变体类型参数,该类型参数对于 生成的构造类型。

        发件人:http://msdn.microsoft.com/en-us/library/dd799517.aspx

        【讨论】:

          猜你喜欢
          • 2013-11-29
          • 2018-12-24
          • 2014-04-30
          • 2018-01-16
          • 2015-10-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多