T 必须是逆变有效的。

46
这有什么问题吗?
interface IRepository<out T> where T : IBusinessEntity
{
    IQueryable<T> GetAll();
    void Save(T t);
    void Delete(T t);
}

它说:

无效的差异:类型参数'T'在'MyNamespace.IRepository.Delete(T)'中必须是逆变有效的。 'T'是协变的。


1
你最终做了什么?我也遇到了同样的问题。那些答案并没有真正解决它;我需要在同一个类中有GetAll、Save和Delete。 - David
1
抱歉,我不记得了。那是4年前的事了。 - Eduardo
3个回答

76

考虑一下如果编译器允许这样的情况会发生什么:

interface IR<out T>
{
    void D(T t);
}

class C : IR<Mammal>
{
    public void D(Mammal m)
    {
        m.GrowHair();
    }
}
...
IR<Animal> x = new C(); 
// legal because T is covariant and Mammal is convertible to Animal
x.D(new Fish()); // legal because IR<Animal>.D takes an Animal

你刚刚试图在一条鱼身上生发。

"out" 的意思是 "T 只能在输出位置使用"。你正在将它用于输入位置。


24
当解释某个东西时,我总是无法理解为什么TIRCx可以作为有效的变量名。这也同样适用于MSDN文档,特别是泛型部分。那么,“D”是什么? - David
10
@David:它们是有效的,因为它们符合C#规范中标识符的标准,但我认为你想说的是教学上的有效性。使用简短名称的教学方法是为了微妙地提醒读者,这是一个通用、广泛适用的示例,他们应该在抽象层面思考,而不是解决特定领域的具体问题的解决方案。 - Eric Lippert
2
不太符合教学规范... 当示例能够让概念形象化,而符号则带来远离概念化的意义。但这与方差相去甚远。 - Luc-Olivier
6
当我第一次尝试学习协变/逆变时,我希望能看到这个解释:"‘out’的意思是'T仅用于输出位置'。你正在一个输入位置使用它。" 我当时只是假设inout是随意重用的关键字。 - Andrew Keeton
3
@AndrewKeeton:我们经历了一个漫长的设计过程,在这个过程中考虑了许多选项。像所有的设计过程一样,有许多相互竞争的因素需要权衡。请查看我在2007年关于此主题的文章,了解一些选项及其利弊:https://blogs.msdn.microsoft.com/ericlippert/2007/10/31/covariance-and-contravariance-in-c-part-eight-syntax-options/ - Eric Lippert
显示剩余2条评论

46

您只能协变地使用out类型参数,即在返回类型中。因此,IQueryable<T> GetAll()是正确的,但void Delete(T t)不是。

由于T在您的类中既用于协变又用于逆变,因此您不能在此处使用out(也不能使用in)。

如果您想了解更多关于这背后的理论知识,请休息一下并阅读"Covariance and Contravariance" Wikipedia article


欢迎回来。那么,如果您需要存储库中的所有这些方法,但仍然需要协变接口,该怎么办呢?您可以将协变部分提取到自己的接口中:

interface IDataSource<out T> where T : IBusinessEntity
{
    IQueryable<T> GetAll();
}

interface IRepository<T> : IDataSource<T> where T : IBusinessEntity
{
    void Save(T t);
    void Delete(T t);
}

这也是.NET BCL解决此问题的方法:`IEnumerable`是协变的,但仅支持“读操作”。`ICollection`是`IEnumerable`的子类型,允许读和写操作,因此本身不能是协变的。

12
请注意,它 可以 在参数中,但仅当使用类似于“Action<T>”这样的函数来再次反转方向时。 - Jon Skeet

24
以下两种方法是错误的:
void Save(T t);
void Delete(T t);

如果您想要在泛型定义中使用协变(out T),则不能将T作为方法参数。只有作为返回类型才能实现协变。

如果您想要逆变性,则只能将泛型参数用作方法参数而不是返回类型:

interface IRepository<in T> where T : IBusinessEntity
{
    void Save(T t);
    void Delete(T t);
}

完整且清晰的答案。谢谢。 - amiry jd
现在我终于明白为什么关键字是 inout。输入到类和从类输出。这是一段时间以来的挣扎,谢谢。 - Shelby115

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接