有人可以为我提供简单的 C# 示例,例如协变、逆变、不变性和逆变性(如果存在这种情况)。
到目前为止,我看到的所有样本都只是将一些对象投射到System.Object
.
有人可以为我提供简单的 C# 示例,例如协变、逆变、不变性和逆变性(如果存在这种情况)。
到目前为止,我看到的所有样本都只是将一些对象投射到System.Object
.
有人可以为我提供简单的 C# 示例,例如协变、逆变、不变性和逆变性(如果存在这种情况)。
我不知道“反不变性”是什么意思。其余的都很容易。
下面是一个协方差的例子:
void FeedTheAnimals(IEnumerable<Animal> animals)
{
foreach(Animal animal in animals)
animal.Feed();
}
...
List<Giraffe> giraffes = ...;
FeedTheAnimals(giraffes);
IEnumerable<T>
接口是协变的。Giraffe 可转换为 Animal 的事实意味着IEnumerable<Giraffe>
可转换为IEnumerable<Animal>
. 由于List<Giraffe>
实现IEnumerable<Giraffe>
此代码在 C# 4 中成功;它在 C# 3 中会失败,因为协方差在IEnumerable<T>
C# 3 中不起作用。
这应该是有道理的。一系列长颈鹿可以被视为一系列动物。
下面是一个逆变的例子:
void DoSomethingToAFrog(Action<Frog> action, Frog frog)
{
action(frog);
}
...
Action<Animal> feed = animal=>{animal.Feed();}
DoSomethingToAFrog(feed, new Frog());
Action<T>
委托是逆变的。Frog 可转换为 Animal 的事实意味着Action<Animal>
可转换为Action<Frog>
. 注意这种关系是如何与协变关系相反的;这就是为什么它是“对立”变体。由于可兑换,此代码成功;它在 C# 3 中会失败。
这应该是有道理的。该动作可以采取任何动物;我们需要一个可以带任何 Frog 的动作,一个可以带任何 Animal 的动作肯定也可以带任何 Frog。
不变性的一个例子:
void ReadAndWrite(IList<Mammal> mammals)
{
Mammal mammal = mammals[0];
mammals[0] = new Tiger();
}
我们可以传递一个IList<Giraffe>
给这个东西吗?不,因为有人要在其中写入老虎,而老虎不能出现在长颈鹿列表中。我们可以传递一个IList<Animal>
到这个东西吗?不,因为我们要从中读取哺乳动物,而动物列表可能包含一只青蛙。 IList<T>
是不变的。它只能按实际情况使用。
有关此功能设计的其他想法,请参阅我关于我们如何设计和构建它的系列文章。
http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/
不变性(在这种情况下)是不存在协方差和反方差。所以反不变这个词没有意义。任何未标记为in
或者out
是不变的类型参数。这意味着该类型参数既可以使用也可以返回。
协方差的一个很好的例子是IEnumerable<out T>
因为 a IEnumerable<Derived>
可以代替IEnumerable<Base>
。或者Func<out T>
返回类型的值T
。
例如 anIEnumerable<Dog>
可以转换为,IEnumerable<Animal>
因为任何 Dog 都是动物。
对于逆变,您可以使用任何消费接口或委托。IComparer<in T>
或Action<in T>
浮现在我的脑海。这些从不返回类型变量T
,只接收它。无论您希望在哪里收到 a Base
,都可以传入 a Derived
。
将它们视为仅输入或仅输出类型参数可以更容易地理解 IMO。
并且不变量这个词通常不与类型变化一起使用,而是在类或方法不变量的上下文中使用,并表示一个守恒属性。请参阅此 stackoverflow 线程,其中讨论了不变量和不变性之间的差异。
如果你考虑泛型的常规使用,你经常使用一个接口来处理一个对象,但是这个对象是一个类的一个实例——你不能实例化接口。以一个简单的字符串列表为例。
IList<string> strlist = new List<string>();
我相信您知道使用 aIList<>
而不是直接使用 a的优势List<>
。它允许控制反转,您可能决定List<>
不再使用 a ,而是想要 a LinkedList<>
。上面的代码工作正常,因为接口和类的泛型类型是相同的:string
.
如果你想创建一个字符串列表,它可能会变得更复杂一些。考虑这个例子:
IList<IList<string>> strlists = new List<List<string>>();
这显然不会编译,因为泛型类型的参数IList<string>
和List<string>
不一样。即使您将外部列表声明为常规类,例如List<IList<string>>
,它也不会编译 - 类型参数不匹配。
所以这里是协方差可以提供帮助的地方。协方差允许您使用更派生的类型作为此表达式中的类型参数。如果IList<>
使其成为协变的,它将简单地编译并修复问题。不幸的是,IList<>
它不是协变的,但它扩展的接口之一是:
IEnumerable<IList<string>> strlists = new List<List<string>>();
此代码现在可以编译,类型参数与上面相同。