泛型的协变和逆变

转载C# 泛型的协变和逆变

1. 可变性的类型:协变性和逆变性

可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用。如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量。协变和逆变是两个相互对立的概念:

  • 如果某个返回的类型可以由其派生类型替换,那么这个类型就是支持协变
  • 如果某个参数类型可以由其基类替换,那么这个类型就是支持逆变的。

2. C# 4.0对泛型可变性的支持

在C# 4.0之前,所有的泛型类型都是不变量——即不支持将一个泛型类型替换为另一个泛型类型,即使它们之间拥有继承关系,简而言之,在C# 4.0之前的泛型都是不支持协变和逆变的。

C# 4.0通过两个关键字:outin来分别支持以协变和逆变的方式使用泛型。

我们来看一段利用了协变类型参数的代码:

public class BaseClass
{
    //...
}

public class DerivedClass : BaseClass
{
    //...
}

下面我们利用协变类型参数,可以执行类似于普通的多态性的分配:

IEnumerable<DerivedClass> d = new List<DerivedClass>();
IEnumerable<BaseClass> b = d;

在上面的实例中,在C# 4.0之前是不能正常编译的,除了对赋值给基类集合时将子类集合做一个强制转换,但是在运行时仍然会抛出一个类型转换的异常。

下面我们再看一个关于逆变的实例代码:

Action<BaseClass> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<DerivedClass> d = b;
d(new DerivedClass());

在上面的示例中我们 Action<BaseClass> 类型的委托分配给类型 Action<DerivedClass> 的变量,根据逆变的定义我们可以知道 Action<T> 类型是支持逆变的。

为什么IEnumerable<T> 和 Action<T> 可以分别支持类型的逆变和协变呢?我们查看这两个类型在 .NET 中的定义:

//IEnumerable<T> 接口的定义(支持协变)
public interface IEnumerable<out T> : IEnumerable

//Action<T> 委托的定义(支持逆变)
public delegate void Action<in T>(T obj);

为了保证类型的安全,C#编译器对使用了 out 和 in 关键字的泛型参数添加了一些限制:

  • 支持协变(out)的类型参数只能用在输出位置:函数返回值、属性的get访问器以及委托参数的某些位置
  • 支持逆变(in)的类型参数只能用在输入位置:方法参数或委托参数的某些位置中出现。

3. C#中泛型可变性的限制

1. 不支持类的类型参数的可变性

只有接口和委托可以拥有可变的类型参数。in 和 out 修饰符只能用来修饰泛型接口和泛型委托。

2. 可变性只支持引用转换

可变性只能用于引用类型,禁止任何值类型和用户定义的转换,如下面的转换是无效的:

  • 将 IEnumerable<int> 转换为 IEnumerable<object> ——装箱转换
  • 将 IEnumerable<short> 转换为 IEnumerable<int> ——值类型转换
  • 将 IEnumerable<string> 转换为 IEnumerable<XName> ——用户定义的转换

3. 类型参数使用了 out 或者 ref 将禁止可变性

对于泛型类型参数来说,如果要将该类型的实参传给使用 out 或者 ref 关键字的方法,便不允许可变性,如:

delegate void someDelegate<in T>(ref T t)

这段代码编译器会报错。

4. 可变性必须显式指定

从实现上来说编译器完全可以自己判断哪些泛型参数能够逆变和协变,但实际却没有这么做,这是因为C#的开发团队认为:

必须由开发者明确的指定可变性,因为这会促使开发者考虑他们的行为将会带来什么后果,从而思考他们的设计是否合理。

5. 注意破坏性修改

在修改已有代码接口的可变性时,会有破坏当前代码的风险。例如,如果你依赖于不允许可变性的is或as操作符的结果,运行在.NET 4时,代码的行为将有所不同。同样,在某些情况下,因为有了更多可用的选项,重载决策也会选择不同的方法。所以在对已有代码引入可变性时要做好足够的单元测试以及防御措施。

6. 多播委托与可变性不能混用

下面的代码能够通过编译,但是在运行时会抛出 ArgumentException 异常:

Func<string> stringFunc = () => "";
Func<object> objectFunc = () => new object();
Func<object> combined = objectFunc + stringFunc;

这是因为负责链接多个委托的 Delegate.Combine方法要求参数必须为相同的类型。上面的示例我们可以修改成如下正确的代码:

Func<string> stringFunc = () => "";
Func<object> defensiveCopy = new Func<object>(stringFunc);
Func<object> objectFunc = () => new object();
Func<object> combined = objectFunc + defensiveCopy;

参考&扩展阅读

协变和逆变
泛型中的协变和逆变
委托中的协变和逆变
《深入理解C#》:13.3 接口和委托的泛型可变性
《Effective C#》:条目29:支持泛型协变和逆变
《CLR via C#》:12.5 委托和接口的逆变和协变泛型类型实参

分类: C#

时间: 2024-12-31 15:48:43

泛型的协变和逆变的相关文章

c#泛型的协变和逆变

例:(引用自Job Log的博客:http://blog.csdn.net/baidu_20993761/article/details/47272597) public delegate TResult Func<out TResult>();// TResult类型是返回值类型public delegate TResult Func<in T, out TResult>(T arg); //输入类型T,返回类型TResultpublic interface IEnumerabl

泛型中协变和逆变

写在前面 今天讲的内容有点多,但是差不多都能听懂,稍微有点模糊的就是协变和逆变的概念,下面是我结合在网上看的资料整合而成的. 正文 msdn上的原话: 协变:是指能够使用比原始指定的派生类型的派生程度更小(不太确定)的类型 逆变:是指能够使用比原始类型的派生类型的派生程度更大(更具体)的类型 在方便理解的概念是: 协变:子类向父类转化,用于返回类型用out关键字 逆变:父类向子类转化的过程,用于方法参数类型用in关键字 协变的例子: 1 public class Person { } 2 3 p

C# 泛型的协变与逆变

那么先讲讲什么是"泛型类协变"你可以这样理解,正常的变换 好比object = String 这样的表达式一样协变主要是向上安全的变换,但是需要根据继承链条一层一层 的向上变化 而不需要进行转换因为是隐式转换,先上一层继承类的代码 不过使 用最容易的理解的,你认为是拆箱与装箱也没什么太大的出入 但是指泛型上的 [csharp] view plaincopy public class Animal // 动物 { } public class Dog : Animal // 狗 { }

c#-泛型、协变、逆变

泛型简单介绍: 可以使用泛型声明的元素:类.接口.方法.委托 泛型之前:泛型之前使用object封装不同类型的参数,缺点:性能差.运行时判断类型(不安全)...泛型是在编译期间转为实际类型副本,所以性能好,还可以使用约束对泛型进行约束 泛型约束:约束泛型类型必须满足约束.使用泛型约束后,可以像使用约束类型的方式使用泛型变量 约束种类:where T:class//引用类型约束where T:new()//无参构造函数约束,加了此约束后可以使用无参构造函数创建实例:T t = new T();wh

泛型--协变与逆变(转)

对于泛型的知识,一直比较模糊,现在有机会整理一下,突发发现C#还有很多你不知道的东东,继续.NET FrameWork中泛型的协变与逆变: 1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如果某个返回的类型可以由其派生类型替换,那么这个类型就是支持协变的 如果某个参数类型可以由其基类替换,那么这个类型就是支持逆变的. 2. C# 4.0对泛型可变性

.NET可变性解析(协变和逆变)

[一]何为可变性 可变性是.NET4.0中的一个新特性,可变性可分为 : 协变性.逆变性.不可变性. 那么在.NET4.0之前是否有可变性? 答案是肯定的,我们可以通过下面的几个实例来简单的了解一下.NET4.0之前的协变和逆变. 实例 1 : 方法参数的协变 static void Main(string[] args) { GetProject(new Course()); // Course 继承自 Project 此处进行了协变 } static void GetProject(Proj

泛型中的协变和逆变

[泛型中的协变和逆变] 协变指能够使用比原始指定的派生类型的派生程度更大的类型,逆变指能够使用比原始指定的派生类型的派生程度更小的类型. 协变与逆变的本质就是参数的替换.逻辑不变,只进行参数的替换,以实现更高程序的复用. 通常,协变类型参数可用作委托的返回类型,而逆变类型参数可用作参数类型. 对于接口,协变类型参数可用作接口的方法的返回类型,而逆变类型参数可用作接口的方法的参数类型. 协变是out,逆变是in. 协变的例子: 逆变的例子,When the delegate of type Act

协变、逆变与不变:数组、泛型、与返回类型

转自:http://blog.csdn.net/yi_Afly/article/details/52071260 1. 前言 之前几篇博文,有些地方涉及到了协变性.逆变性与不变性在Java中的表现,所以这篇博文将重点记录这方面的内容,并辅以JDK源码中的一些实例,加以说明. 2. 定义 这里讨论的协变.逆变与不变都是编程语言中的概念.下面介绍定义: 若类A是类B的子类,则记作A ≦ B.设有变换f(),若: 当A ≦ B时,有f(A)≦ f(B),则称变换f()具有协变性. 当A ≦ B时,有f

厘清泛型参数的协变与逆变

协变与逆变(CoVariant and ContraVariant),很多人是糊涂的,我也一直糊涂.其实,对协变与逆变概念糊涂,甚至完全不知道,对一般程序员也没有很大影响.不过,如果你想提高水平,想大概看懂.Net Framework类库中那些泛型接口与泛型类,想大概弄清楚Linq,这个概念还是需要搞清楚. 话又说回来,想弄清楚,其实还是挺费劲的. 如果你还糊涂着这两个概念,相信我,认真看完下面的文字,你会对泛型参数的协变与逆变有一个清晰的理解. 想透彻掌握协变.逆变的概念,首先需要对接口.委托