方差有效性的确切规则有点模糊,不具体。我将列出使类型有效的规则-协变,并为每个规则附加一些查询和个人注释。
如果满足以下条件,则类型协变有效
1)指针类型,或非泛型类型。
指针和非泛型类型在c#中没有变化,除了数组和非泛型委托。泛型类、结构和枚举是不变的。我说的对吗?
2)数组类型T[],其中T是协变有效的。
所以这意味着如果数组T[]
的元素类型T
是协变的(引用或数组元素类型),则该数组是协变的,如果元素类型是不变的(值类型),则该数组类型是不变的。数组在c#中不能是逆变的。我说的对吗?
3)如果未声明为逆变,则为泛型类型参数类型。
我们通常说泛型类型是形参类型的变体,但对于形参类型来说,它是自己的变体。这是这句话的另一种简称吗?例如,泛型T<out D>
对D
是协变的(因此协变有效),因此我们可以说类型参数D
是协变有效的。我说的对吗?
一个构造的类、结构、枚举、接口或委托类型X可能协变有效。为了确定它是否存在,我们以不同的方式检查每个类型实参,这取决于相应的类型形参是被声明为协变(out)、逆变(in)还是不变(两者都不是)。(当然,类和结构的泛型类型参数永远不会被声明为'out'或'in';它们永远是不变的。)如果第i个类型参数被声明为协变,那么Ti必须协变有效。如果它被声明为逆变,那么Ti一定是逆变有效的。如果它被声明为不变的,那么Ti一定是不变有效的。
最后一条规则,从上到下,是完全模糊的。
我们讨论的是泛型类型在所有入/出/不变类型参数上的方差吗?根据定义,泛型一次可以在一个类型参数上是协变/逆变/不变的。协变或不变,在这种情况下,它的所有类型参数一次都是协变或不变,没有任何意义。这是什么意思呢?
前进。为了确定泛型类型是否协变有效,我们检查它的类型实参(不是类型形参)。因此,如果对应的类型参数为协变/逆变/不变,则类型参数分别为协变/逆变/不变有效…
我需要更深入地解释这条规则。
编辑:谢谢Eric。非常感激!
我完全理解有效的协变/逆变/不变是什么意思。一个类型是有效的,如果它绝对不是逆变的,这意味着它可以是不变的。非常好!
对于第4条规则,您遵循如何确定构造的泛型类型是否协变有效的过程,如规则中定义的那样。但是,如何确定一个被声明为协变(out)的类型参数是否协变有效?
例如,在封闭构造接口I {}的泛型接口I{…}, 类型参数object
在泛型接口声明中被声明为协变类型参数(out U)这一事实难道不应该意味着类型参数对象是协变的吗?我想应该是这样。因为这就是协变的定义。
同样,第二条规则:
2)数组类型T[],其中T是协变有效的。
数组元素类型T
协变有效意味着什么?您的意思是元素类型是值类型(在这种情况下是不变的)还是引用类型(在这种情况下是协变的)?
因为投影T
→只有当T
是引用类型时,T[]
才是变量。
你是对的,最后一条规则是最难理解的,但我向你保证它并不含糊。
举一两个例子会有帮助。考虑以下类型声明:
interface I<in T, out U, V> { ... }
这个类型协变有效吗?
I<string, object, int> { }
让我们看一下我们的定义。
要确定是否存在,根据相应的类型形参是被声明为协变(out)、逆变(in)还是不变(两者都不是),对每个类型实参进行不同的检查。
好的,所以类型参数是string
, object
和int
。对应的参数分别为in T
、out U
、V
。
如果第i个类型参数被声明为协变(
out
),那么Ti必须协变有效。
第二个类型参数是out U
,所以object
一定是协变有效的。它是。
如果它被声明为逆变(
in
),那么Ti一定是逆变有效的。
第一个被声明为in T
,因此string
必须是反向有效的。它是。
如果它被声明为不变量,那么Ti一定是不变有效的。
第三个V
是不变的,所以int
一定是不变有效的;它必须是协变和逆变有效的。它是。
我们通过了所有三张支票;类型I<string, object, int>
是协变有效的。
好吧,这个很简单。
现在让我们看一个更难的。
interface IEnumerable<out W> { ... }
interface I<in T, out U, V>
{
IEnumerable<T> M();
}
I
中的IEnumerable<T>
是一个类型。IEnumerable<T>
内使用I
有效协变?
让我们看一下定义。类型实参T
对应于类型形参out W
。T
是I
的类型参数, IEnumerable
的类型参数。
如果第i个类型参数(
W
)被声明为协变参数(out
),那么Ti (T
)一定是协变有效的。
OK,所以I
中的IEnumerable<T>
协变有效,T
必须协变有效。是吗?不。将T
声明为in T
。声明为in
的类型参数在协变上永远无效。因此,I
内部使用的IEnumerable<T>
类型在协变上是无效的,因为违反了"must"条件。
再一次,就像我在回答你之前的问题时说的,如果"有效协变"one_answers"有效逆变"让你感到悲伤,那就给它们起个不同的名字。它们是定义良好的形式属性;你可以叫它们任何你想叫的名字,如果这样更容易理解的话。
不,你的注释乱成一团
那篇文章真的很难理解,部分原因是"有效协方差"与协方差完全没有关系。Eric确实指出了这一点,但这意味着对于每个句子,你都必须"取消思考"自然意义,然后根据"协变有效"、"逆变有效"one_answers"不变有效"这些奇怪的定义来思考。
我强烈建议你阅读一下Liskov替代原则,然后再考虑可替代性。从LSP的角度来看,协方差、逆变性和不变性的定义非常简单。
然后,你可能会注意到c#在编译时的规则与LSP并不完全匹配(不幸的是,这主要是在Java中犯的错误,并复制到c#中以帮助Java程序员)。另一方面,在运行时必须遵循LSP规则,所以如果你从这些开始,你将编写既编译又正确运行的代码,我认为这比学习c#语言规则(除非你正在编写c#编译器)更有价值。如何确定一个类型参数被声明为协变(out)是否协变有效?
读取规则3
泛型接口I<in T, out U, V>
的封闭构造接口I{string, object int>
中的,类型参数
object
在泛型接口声明中被声明为协变类型参数out U
,这一事实难道不应该意味着类型参数object
是协变的吗?
首先,你用"协变"来表示"协变有效"。记住,这是不同的东西。
第二,让我们再看一遍。object
协变有效吗?是的,根据第一条规则。I<string, object, int>
协变有效吗?是的,根据规则3,它说:
- 与T对应的类型参数必须是逆变有效的。
- 与U对应的类型参数必须协变有效。
- 与V对应的类型参数必须两者都是。
由于所有三个条件都满足,I<string, object, int>
是协变有效的。
我不明白这个问题。我们正在定义什么是"协变有效"。规则2是"协变有效"定义的一部分。在"数组类型T[]其中T是有效的协变"中,数组元素类型T是有效的协变意味着什么?
作为一个例子,object[]
协变有效吗?是的,因为object
是协变有效的。如果我们有:
interface IFoo<out T> { T[] M(); }
T[]
协变有效吗?是的,因为T
是协变有效的。
如果有
interface IBar<in T> { T[] M(); }
T[]
协变有效吗?不。对于一个协变有效的数组类型,它的元素类型必须是协变有效的,但是T
不是。