10

方差有效性的确切规则有点模糊且不具体。我将列出使类型有效协变的规则,并将一些查询和个人注释附加到每个规则中。

一个类型是协变有效的,如果它是:

1) 指针类型,或非泛型类型。

指针和非泛型类型在 C# 中不是变体,但数组和非泛型委托除外。泛型类、结构和枚举是不变的。我在这里吗?

2) 数组类型 T[],其中 T 协变有效。

所以这意味着如果T数组的元素类型T[]是协变的(引用或数组元素类型),那么数组是协变的,如果元素类型是不变的(值类型),那么数组类型是不变的。数组在 C# 中不能逆变。我在这里吗?

3) 泛型类型参数类型,如果它没有被声明为逆变的。

我们通常说泛型类型是参数类型的变体,但参数类型本身就是变体。这是另一种说法吗?例如,泛型类型T<out D>是协变的D(因此是协变有效的),因此我们可以说类型参数D是协变有效的。我对吗?

4) 构造的类、结构、枚举、接口或委托类型 X 可能协变有效。为了确定它是否是,我们以不同的方式检查每个类型参数,具体取决于相应的类型参数是否被声明为协变(out)、逆变(in)或不变(两者都不是)。(当然,类和结构的泛型类型参数永远不会被声明为“out”或“in”;它们将始终是不变的。)如果第 i 个类型参数被声明为协变,那么 Ti 必须协变有效。如果它被声明为逆变的,那么 Ti 必须是逆变有效的。如果它被声明为不变,那么 Ti 必须是不变有效的。

最后一条规则,从上到下,完全是模棱两可的。

我们是在谈论泛型类型在其所有输入/输出/不变类型参数上的差异吗?根据定义,泛型类型一次可以在一个类型参数上是协变/逆变/不变的。在这种情况下,要成为协变或不变的,同时对它的所有类型参数没有任何意义。那意味着什么?

向前进。为了确定泛型类型是否协变有效,我们检查它的类型参数(不是类型参数)。所以如果对应的类型参数是协变/逆变/不变的,那么类型参数分别是协变/逆变/不变有效...

我需要更深入地解释这条规则。


编辑:谢谢埃里克。非常感激!

我完全理解有效的协变/逆变/不变的含义。一个类型是协变有效的,如果它绝对不是逆变的,这意味着它可以是不变的。完全没问题!

对于第 4 条规则,您按照规则中定义的如何确定构造的泛型类型是否协变有效的过程。但是,如何确定声明为协变 (out) 的类型参数是否协变有效?

例如,在通用接口 I { ... }的封闭构造接口 I { }中,类型参数在通用接口声明中被声明为协变类型参数(out U)这一事实不应该意味着类型参数对象是协变的?我认为应该。因为这就是协变的定义。object

另外,第二条规则:

2) 数组类型 T[],其中 T 协变有效。

数组元素类型T有效协变意味着什么?您的意思是元素类型是值类型(在这种情况下是不变的)还是引用类型(在这种情况下是协变的)?

因为投影T→只有在引用类型T[]时才变体。T

4

3 回答 3

13

你是对的,最后一条规则是最难理解的,但我向你保证,它不是模棱两可的。

一两个例子会有所帮助。考虑这个类型声明:

interface I<in T, out U, V> { ... }

这种类型协变有效吗?

I<string, object, int> { }

让我们来看看我们的定义。

为了确定它是否是,我们以不同的方式检查每个类型参数,具体取决于相应的类型参数是否被声明为协变(out)、逆变(in)或不变(两者都不是)。

好的,所以类型参数是string,objectint。对应的参数分别是in Tout UV

如果第 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();
}

IEnumerable<T>insideI是一个类型。是否IEnumerable<T>I有效协变内使用?

让我们来看看我们的定义。我们有T对应于类型参数的类型参数out W。请注意,它T是 的类型参数和的I类型参数IEnumerable

如果第 i 个类型参数 ( W) 被声明为协变 ( out),则 Ti ( T) 必须协变有效。

好的,所以为了IEnumerable<T>I变有效,T必须协变有效。是吗?不。T被宣布为in T。声明的类型参数in永远不会协变有效。因此,IEnumerable<T>内部使用的类型I不是协变有效的,因为违反了“必须”条件。

同样,就像我在回答您之前的问题时所说的那样,如果“有效协变”和“有效逆变”让您感到悲伤,请给它们不同的名称。它们是定义明确的形式属性;如果这样更容易理解,您可以随意称呼它们。

于 2013-04-27T00:31:00.087 回答
4

不,你的注释搞砸了。

那篇文章真的很难理解,部分原因是“有效协变”与协变完全无关。Eric 确实指出了这一点,但这意味着对于每个句子,您必须“不考虑”自然含义,然后根据“有效协变”、“有效逆变”和“有效不变”的这些奇怪定义来思考。

我强烈建议您阅读 Liskov 替代原则,然后考虑可替代性。从 LSP 的角度来看,协变、逆变和不变性的定义非常简单。

然后,您可能会注意到编译时的 C# 规则与 LSP 不完全匹配(不幸的是——这主要是 Java 中的一个错误,并被复制到 C# 中以帮助吸引 Java 程序员)。另一方面,在运行时必须遵循 LSP 规则,因此如果您从这些开始,您将编写既能正确编译又能正确运行的代码,我认为这比学习 C# 语言规则更有价值(除非你正在编写一个 C# 编译器)。

于 2013-04-26T23:53:40.853 回答
4

您如何确定声明为协变(出)的类型参数是否协变有效?

阅读规则 3。

在泛型接口的封闭构造接口I{string, object int>中,类型实参在泛型接口声明中被声明为协变类型参数I<in T, out U, V>这一事实不应该意味着类型实参是协变的吗?objectout Uobject

首先,您使用的是“协变”,您的意思是“协变有效”。请记住,这些是不同的东西。

其次,让我们再过一遍。object协变有效吗?是的,根据规则 1。I<string, object, int>协变有效吗?是的,第 3 条规定:

  • 对应于 T 的类型参数必须是逆变有效的。
  • 对应于 U 的类型参数必须是协变有效的。
  • V 对应的类型参数必须是两者。

由于所有三个条件都满足,I<string, object, int>因此是协变有效的。

在“其中 T 协变有效的数组类型 T[]”中,数组元素类型 T 协变有效是什么意思?

我不明白这个问题。我们正在定义“协变有效”的含义。规则 2 是“协变有效”定义的一部分。

例如,object[]协变有效吗?是的,因为object是协变有效的。如果我们有:

interface IFoo<out T> { T[] M(); }

T[]协变有效吗?是的,因为T是协变有效的。

如果我们有

interface IBar<in T> { T[] M(); }

T[]协变有效吗?不。要使数组类型协变有效,其元素类型必须协变有效,但T不是。

于 2013-04-27T06:44:52.157 回答