逆变值类型

时间:2013-08-07 11:55:07

标签: c# .net contravariance

我为我的存储库创建了这个界面。

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方法接受一个I​​D,该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我可以传递任何类型,因为它仍然是通用的。

这可能表现出对逆变和协方差的理解不足,但它让我有些困惑。

4 个答案:

答案 0 :(得分:6)

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

public struct MyStruct<in T>

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

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

由于没有任何内容来自int,您可以使用IRepository<string, int>,但只能使用IRepository<string, int>

协方差意味着您可以反过来,例如IEnumerable<T>out T,是协变的。您可以执行以下操作:

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

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

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

答案 1 :(得分: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<IBar>的实例分配给变量referenceTypeRepository,因为BarReferenceType实现了IBar
您引用的MSDN中的部分仅表示将IRepository<IBar>的实例分配给valueTypeRepository是不合法的,尽管BarValueType也实现了IBar

答案 2 :(得分:1)

使用值类型实现接口没有问题。例如,尝试将IRepository<Project, object>分配给IRepository<Project, int>时,您只会收到错误消息。在以下代码中,最后一个赋值将无法编译:

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

答案 3 :(得分:0)

在本文中,他们告诉我们编译器将type参数视为不变

  

差异仅适用于参考类型;如果指定值类型   对于变体类型参数,该类型参数为不变   结果构造类型。

来自:http://msdn.microsoft.com/en-us/library/dd799517.aspx