`

C# 4.0新特性——“协变”与“逆变”以及背后的编程思想

    博客分类:
  • c#
阅读更多

2011-01-14 09:47 |  2265次阅读 |  来源:博客园   【已有0条评论】发表评论

关键词:C#,协变,逆变,编程 |  感谢张祺的提供 |  收藏这篇资讯

导读:作者蒋金楠,网名Artech。解决方案架构与互联系统MVP,微软最有影响力开发者。在《谈谈C# 4.0新特性“缺省参数”的实现》中我们揭示了“缺省参数”的本质,现在我们接着来谈谈C#4.0中另一个重要的新特性:协变(Covariance)与逆变(Contravariance)。对于协变与逆变,大家肯定不会感到陌生,但是我相信有很多人不能很清晰地说出他们之间的区别。我希望通过这篇文章能够让读者更加深刻的认识协变与逆变。以下是文章内容:

一、两个概念:强类型与弱类型

 

为了后面叙述方便,我现在这里自定义两个概念:强类型和弱类型。在本篇文章中,强类型和弱类型指的是两个具有直接或者间接继承关系的两个类。如果一个类是另一个类的直接或者间接基类,那么它为弱类型,直接或者间接子类为强类型。后续的介绍中会用到的两个类Foo和Bar先定义在这里。Bar继承自Foo。 Foo是弱类型,而Bar则是强类型。

 

   1: public class Foo

   2: {

   3:     //Others Members...

   4: }

   5: public class Bar:Foo

   6: {

   7:     //Others Members...

   8: }

 

有了强类型和弱类型的概念,我们就可以这样的定义协变和逆变:如果类型TBar是基于强类型Bar的类型(比如类型参数为Bar的泛型类型,或者是参数/返回值类型为Bar的委托),而类型TFoo是基于弱类型Foo的类型,协变就是将TBar类型的实例赋值给TFoo类型的变量,而逆变则是将TFoo类型的实例赋值给TBar类型的变量。

二、委托中的协变与逆变的使用

 

协变和逆变主要体现在两个地方:接口和委托,先来看看在委托中如何使用协变和逆变。现在我们定义了如下一个表示无参函数的泛型委托Function<T>,类型参数为函数返回值的类型。泛型参数之前添加了一个out关键字表示T是一个协变变体。那么在使用过程中,基于强类型的委托Fucntion<Bar>实例就可以赋值给基于弱类型的委托 Fucntion<Foo>变量。

 

   1: public delegate T Function<out T>();

   2: class Program

   3: {

   4:     static void Main()

   5:     {

   6:         Function<Bar> funcBar = new Function<Bar>(GetInstance);

   7:         Function<Foo> funcFoo = funcBar;

   8:         Foo foo = funcFoo();

   9:     }

  10:     static Bar GetInstance()

  11:     {

  12:         return new Bar();

  13:     }

  14: }

 

接下来介绍逆变委托的用法。下面定义了一个名称为Operate的泛型委托,接受一个具有泛型参数类型的参数。在定义泛型参数前添加了in关键字,表示T是一个基于逆变的变体。由于使用了逆变,我们就可以将基于弱类型的委托 Operate<Foo>实例就可以赋值给基于强类型的委托Operate<Bar>变量。

 

   1: public delegate void Operate<in T>(T instance);

   2: class Program

   3: {

   4:     static void Main()

   5:     {

   6:         Operate<Foo> opFoo = new Operate<Foo>(DoSth);

   7:         Operate<Bar> opBar = opFoo;

   8:         opBar(new Bar());

   9:     }

  10:     static void DoSth(Foo foo)

  11:     {

  12:         //Others...

  13:     }

  14: }

 

三、接口中的协变与逆变的使用

 

接下来我们同样通过一个简单的例子来说明在接口中如何使用协变和逆变。下面定义了一个继承自 IEnumerable<T>接口的IGroup<out T>集合类型,和上面一样,泛型参数T之前的out关键字表明这是一个协变。既然是协变,我们就可以将一个基于强类型的委托 IGroup<Bar>实例就可以赋值给基于弱类型的委托IGroup<Foo>变量。

 

   1: public interface IGroup<out T> : IEnumerable<T>

   2: { }

   3: public class Group<T> : List<T>, IGroup<T>

   4: { }

   5: public delegate void Operate<in T>(T instance);

   6: class Program

   7: {

   8:     static void Main()

   9:     {

  10:         IGroup<Bar> groupOfBar = new Group<Bar>();

  11:         IGroup<Foo> groupOfFoo = groupOfBar;

  12:         //Others...

  13:     }

  14: }

 

下面是一个逆变接口的例子。首先定义了一个IPaintable的接口,里面定义了一个可读写的 Color属性,便是实现该接口的类型的对象具有自己的颜色,并可以改变颜色。类型Car实现了该接口。接口IBrush<in T>定义了一把刷子,泛型类型需要实现IPaintable接口,in关键字表明这是一个逆变。方法Paint用于将指定的对象粉刷成相应的颜色,表示被粉刷的对象的类型为泛型参数类型。Brush<T>实现了该接口。由于IBrush<in T>定义成逆变,我们就可以将基于强类型的委托IBrush<Car>实例就可以赋值给基于弱类型的委托 IBrush<IPaintable>变量。

 

   1: public interface IPaintable

   2: {

   3:     Color Color { get; set; }

   4: }

   5: public class Car : IPaintable

   6: {

   7:     public Color Color { get; set; }

   8: }

   9: 

  10: public interface IBrush<in T> where T : IPaintable

  11: {

  12:     void Paint(T objectToPaint, Color color);

  13: }

  14: public class Brush<T> : IBrush<T> where T : IPaintable

  15: {

  16:     public void Paint(T objectToPaint, Color color)

  17:     {

  18:         objectToPaint.Color = color;

  19:     }

  20: }

  21: 

  22: class Program

  23: {

  24:     static void Main()

  25:     {

  26:         IBrush<IPaintable> brush = new Brush<IPaintable>();

  27:         IBrush<Car> carBrush = brush;

  28:         Car car = new Car();

  29:         carBrush.Paint(car, Color.Red);

  30:         Console.WriteLine(car.Color.Name);

  31:     }

  32: }

 

四、从Func<T,TResult>看协变与逆变的本质

 

接下来我们来谈谈协变和逆变的本质区别是什么。在这里我们以我们非常熟悉的一个委托 Func<T, TResult>作为例子,下面给出了该委托的定义。我们可以看到Func<T, TResult>定义的两个泛型参数分别属于逆变和协变。具体来说输入参数类型为逆变,返回值类型为协变。

 

   1: public delegate TResult Func<in T, out TResult>(T arg);

 

再重申以下这句话“输入参数类型为逆变,返回值类型为协变”。然后,你再想想为什么逆变用in关键字,而协变用out关键字。这两个不是偶然,实际上我们可以将协变/逆变与输出/输入匹配起来。

 

我们再从另一个角度来理解协变与逆变。我们知道接口代表一种契约,当一个类型实现一个接口的时候就相当于签署了这份契约,所以必须是实现接口中所有的成员。实际上类型继承也属于一种契约关系,基类定义契约,子类“签署”该契约。对于类型系统来说,接口实现和类型继承本质上是一致的。契约是弱类型,签署这份契约的是强类型。

 

将契约的观点应用在委托上面,委托实际上定义了一个方法的签名(参数列表和返回值),那么参数和返回值的类型就是契约,现在的关键是谁去履行这份契约。所有参数是外界传入的,所以基于参数的契约履行者来源于外部,也就是被赋值变量的类型,所以被赋值变量类型是强类型。而对于代理本身来说,参数是一种输入,也就是一种采用in关键字表示的逆变。

 

而对于委托的返回值,这是给外部服务的,是委托自身对外界的一种承诺,所以它自己是契约的履行着,因此它自己应该是强类型。相应地,对于代理本身来说,返回值是一种输出,也就是一种采用out关键字定义的协变。

 

也正式因为这个原因,对于一个委托,你不能将参数类型定义成成协变,也不能将返回类型定义成逆变。下面两中变体定义方式都是不能通过编译的。

 

   1: delegate TResult Fucntion<out T, TResult>(T arg);

 

   2: delegate TResult Fucntion<T, in TResult>(T arg);

 

说到这里,我想有人要问一个问题,既然输入表示逆变,输出表示协变,委托的输出参数应该定义成协变了?非也,实际上输出参数在这里既输出输出,也输出输入(毕竟调用的时候需要指定一个对应类型的对象)。也正是为此,输出参数的类型及不能定义成协变,也不能定义成逆变。所以下面两种变体的定义也是不能通过编译的。

 

   1: delegate void Action<in T>(out T arg);

 

   2: delegate void Action<out T>(out T arg);

 

虽然这里指介绍了关于委托的协变与逆变,上面提到的契约和输入/输出的关系也同样适用于基于接口的协变与逆变。你自己可以采用这样的方式去分析上面一部分我们定义的IGroup<Foo>和IBrush<in T>。

五、逆变实现了“算法”的重用

 

实际上关系协变和逆变体现出来的编程思想,还有一种我比较推崇的说法,那就是:协变是继承的体现,而逆变体现的则是多态。实际上这与上面分析的契约关系本质上是一致的。

 

关于逆变,在这里请容我再啰嗦一句:逆变背后蕴藏的编程思想体现出了对算法的重用——我们为基类定义了一套操作,可以自动应用于所有子类的对象。

原文链接:http://www.cnblogs.com/artech/archive/2011/01/13/1934914.html

分享到:
评论

相关推荐

    C#4.0新特性之协变与逆变实例分析

    本文实例讲述了C#4.0新特性的协变与逆变,有助于大家进一步掌握C#4.0程序设计。具体分析如下: 一、C#3.0以前的协变与逆变 如果你是第一次听说这个两个词,别担心,他们其实很常见。C#4.0中的协变与逆变(Covariance...

    C#4.0的一些新特性

    ### C#4.0的新特性详解 随着Visual Studio 2010的正式发布,C#4.0作为其中的一项重要更新,引入了一系列新的特性,这些特性极大地提升了开发者的编程体验,并增强了C#语言的功能性。本文将详细介绍C#4.0中的两个...

    C# 4.0 的4个新特性

    ### C# 4.0 的四个新特性详解 随着C# 4.0版本的发布,微软为开发者带来了一系列令人兴奋的新功能。本文将详细解析其中的四个关键特性:动态类型支持、并行编程增强、隐式接口实现以及空安全操作符。 #### 一、动态...

    c# 4.0新特性一览

    C# 4.0是微软开发的面向对象的编程语言C#的重要版本更新,它引入了一系列新特性,旨在提升开发者的生产力和代码的灵活性。在这一版本中,C#开始更加紧密地与动态语言集成,同时也对现有特性进行了增强。 **动态类型...

    C#4.0新特性.doc

    在C# 4.0中,引入了类型安全的变性概念,使得泛型接口和委托可以支持协变和逆变。这意味着,例如,`IEnumerable&lt;string&gt;`现在可以被视为`IEnumerable&lt;object&gt;`。这一特性增强了泛型类型在不同上下文中的兼容性,...

    C#4.0新特性介绍

    ### C#4.0新特性介绍 随着C#3.0作为Visual Studio 2008的一部分发布,Microsoft持续在推进C#语言的发展。C#4.0是继C#3.0之后的一个重要版本,它引入了一系列的新特性,旨在提高开发者的生产力,并更好地与其他动态...

    C#4.0语言规范 C#4.0语言规范C#4.0语言规范

    5. **泛型协变和逆变**:在C# 4.0中,泛型接口可以声明为协变或逆变,允许在泛型集合间更自由地进行赋值和方法签名的匹配,特别是在处理多态数据时。 6. **匿名类型**:C# 4.0增强了匿名类型的功能,可以用于临时...

    C#4.0新特性中文帮助文档

    C#4.0是.NET Framework 4的一部分,它在C#3.0的基础上引入了一些显著的新特性和改进,旨在提高编程效率和灵活性。这个中文帮助文档详细解释了这些新特性,帮助开发者更好地理解和利用C#4.0进行软件开发。 1. **动态...

    c#4.0新特性

    ### C#4.0新特性详解 #### Introduction 简介 自从Microsoft Visual C# 3.0作为Visual Studio 2008的一部分发布以来,已经过去了近一年的时间。在此期间,VS Managed Languages团队一直在致力于开发该语言的下一个...

    c# 4.0新特性详解

    总之,C# 4.0通过引入动态类型、命名和可选参数、改进的委托处理等新特性,显著地扩展了其功能范围,使其在与动态语言交互和编写简洁代码方面有了更强的能力。这些变化反映了C#设计团队不断努力适应开发者需求,提高...

    C#4.0新特性源码

    这个名为"C#4.0新特性源码"的压缩包很可能是微软官方提供的示例代码,用于帮助开发者理解和实践这些新特性。下面,我们将详细探讨这些关键特性。 1. **动态类型(Dynamic Type)**:C# 4.0引入了`dynamic`关键字,...

    C#4.0权威指南

    除了介绍C# 4.0的新特性之外,《C# 4.0权威指南》还涵盖了所有C#的基本知识点和高级主题,确保无论是新手还是有经验的开发人员都能从中受益。它不仅关注语法层面的介绍,更重要的是强调如何正确地应用这些特性来解决...

    C#4.0规范中文版PDF

    4. **协变和逆变(Covariance and Contravariance)**:C# 4.0支持接口和委托的协变和逆变,这使得类型转换更加灵活,特别是对于泛型接口和委托,能够提高代码的复用性。 5. **多语言互操作(Multilingual ...

    C#4.0权威指南电子书

    《C#4.0权威指南》是一本深受程序员喜爱的C#编程教程,全面而深入地介绍了C# 4.0版本的各种特性和技术。这本书不仅适合初学者,也适合有一定经验的开发者,帮助他们提升在.NET框架下使用C#进行软件开发的专业技能。 ...

    全面揭秘 c# 4.0

    《全面揭秘C# 4.0》是一本深入解析C#编程语言第四版的重要书籍,由业界专家撰写,旨在帮助开发者全面理解并掌握C# 4.0的新特性和改进。C# 4.0是.NET Framework的重要组成部分,它带来了许多增强的功能,使得开发更加...

    C#4.0新特性之(一)动态查找

    C#4.0新特性之新特性之新特性之新特性之(一一一一)动态查找

    C#4.0权威指南 源代码

    4. **改进的泛型**:C# 4.0引入了协变和逆变的概念,使泛型接口和类在特定情况下可以接受更广泛的类型参数,提高了代码的可重用性。 5. **互操作性增强**:通过改进的COM互操作,C# 4.0更好地支持与非托管代码的...

Global site tag (gtag.js) - Google Analytics