关于c#:逆变值类型

Contravariant Value Types

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

1
2
3
4
5
6
7
8
9
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,它指定这不应该起作用

Covariance and contravariance in generic type parameters are supported for reference types, but they are not supported for value types.

我创建的类如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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文档说,反向变量允许使用派生较少的类型,但是通过删除EDOCX1[1]我可以传入任何类型,因为它仍然是泛型的。

这可能显示出对协方差和协方差的理解不足,但这让我有点困惑。


协方差和反方差在值类型上没有那么有意义,因为它们都是密封的。虽然从文档中不清楚,但是使用struct作为co/contravariant类型是有效的,它并不总是有用的。您引用的文档很可能是指以下内容无效:

1
public struct MyStruct<in T>

反向变化意味着你可以做如下的事情:

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

由于没有来自int的任何内容,所以可以使用IRepository,但只能作为IRepository使用。

协方差意味着你可以做相反的事情,例如,IEnumerableout T,它是协变的。您可以执行以下操作:

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

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

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


实际上,您缺少了co-和contravariance的整个点:-)这是关于能够将一个泛型类型的变量赋给同一个泛型类型的另一个变量,但具有与源中使用的变量相关的不同的泛型类型参数。根据泛型类型参数是同变量还是反变量,允许不同的赋值。

假设以下接口:

1
2
3
4
public interface IRepository<in T>
{
    void Save(T value);
}

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

1
2
3
4
5
6
7
8
9
10
11
public interface IBar
{
}

public struct BarValueType : IBar
{
}

public class BarReferenceType : IBar
{
}

最后,假设两个变量:

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

相反现在意味着您可以将IRepository的一个实例分配给变量referenceTypeRepository,因为BarReferenceType实现IBar。您引用的msdn中的部分仅意味着将IRepository的一个实例分配给valueTypeRepository是不合法的,尽管BarValueType也执行IBar


使用值类型实现接口没有问题。例如,只有在尝试将IRepository分配给IRepository时才会出现错误。在下面的代码中,上一个分配将不会编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
    }
}

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

Variance applies only to reference types; if you specify a value type
for a variant type parameter, that type parameter is invariant for the
resulting constructed type.

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