• 《C#高级编程》学习笔记------抗变和协变


    1.协变和抗变

    在.NET 4之前,泛型接口是不变的。.NET 4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展。协变和抗变指对参数和返回值的类型进行转换。例如,可以给一个需要Shape参数的方法传送Rectangle参数码?下面用示例说明这些扩展的优点。

    在.NET中,参数类型是协变的。假定有Shape和Rectangle类,Rectangle派生自Shape基类。声明Display()方法是为了接受Shape类型的对象作为其参数:

    public void Display(Shape o) { } 

    现在可以传递派生自Shape基类的任意对象。因为Rectangle派生自Shape,所以Rectangle满足Shape的所有要求,编译器接受这个方法调用:

    Rectangle r = new Rectangle { Width= 5, Height=2.5};  
    Display(r); 

    方法的返回类型是抗变的。当方法返回一个Shape时,不能把它赋予Rectangle,因为Shape不一定总是Rectangle。反过来是可行的:如果一个方法像GetRectangle()方法那样返回一个Rectangle,

    public Rectangle GetRectangle(); 

    就可以把结果赋予某个Shape:

    Shape s = GetRectangle(); 

    在.NET Framework 4版本之前,这种行为方式不适用于泛型。在C# 4中,扩展后的语言支持泛型接口和泛型委托的协变和抗变。下面开始定义Shape基类和Rectangle类:

    public class Shape  
    {  
    public double Width { get; set; }  
    public double Height { get; set; }  
    public override string ToString()  
    {  
    return String.Format("Width: {0}, Height: {1}", Width, Height);  
    }  
    }  
    public class Rectangle: Shape  
    {  
    }

    2.泛型接口的协变

    如果泛型类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。接口IIndex与类型T是协变的,并从一个只读索引器中返回这个类型:

    public interface IIndex<out T> 
    {  
    T this[int index] { get; }  
    int Count { get; }  
    } 

    代码段Variance/IIndex.cs

    如果对接口IIndex使用了只读索引器,就把泛型类型T传递给方法,并从方法中检索这个类型。这不能通过协变来实现-- 泛型类型必须定义为不变的。不使用out和in标注,就可以把类型定义为不变的。

    IIndex<T>接口用RectangleCollection类来实现。RectangleCollection类为泛型类型T定义了Rectangle:

    public class RectangleCollection: IIndex<Rectangle> 
    {  
    private Rectangle[] data = new Rectangle[3]  
    {  
      new Rectangle { Height=2, Width=5},  
      new Rectangle { Height=3, Width=7},  
      new Rectangle { Height=4.5, Width=2.9}  
    };  
    public static RectangleCollection GetRectangles() {   return new RectangleCollection(); }
    public Rectangle this[int index] {   get   {   if (index < 0 || index > data.Length)   throw new ArgumentOutOfRangeException("index");   return data[index];   } }
    public int Count {   get   {   return data.Length;   } } }

    代码段Variance/RectangleCollection.cs

    RectangleCollection.GetRectangle()方法返回一个实现IIndex<Rectangle>接口的RectangleCollection类,所以可以把返回值赋予IIndex<Rectangle>类型的变量rectangle。因为接口是协变的,所以也可以把返回值赋予IIndex<Shape>类型的变量。Shape不需要Rectangle没有提供的内容。使用shapes变量,就可以在for循环中使用接口中的索引器和Count属性:

    static void Main()  
    {  
    IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles();  
    IIndex<Shape> shapes = rectangles;  
      for (int i = 0; i < shapes.Count; i++)  
      {  
      Console.WriteLine(shapes[i]);  
      }  
    } 

    代码段Variance/Program.cs

    3.泛型接口的抗变

    如果泛型类型用in关键字标注,泛型接口就是抗变的。这样,接口只能把泛型类型T用作其方法的输入:

    public interface IDisplay<in T> 
    {  
    void Show(T item);  
    }  

    代码段Variance/IDisplay.cs

    ShapeDisplay类实现IDisplay<Shape>,并使用Shape对象作为输入参数:

    public class ShapeDisplay: IDisplay<Shape> 
    {  
    public void Show(Shape s)  
    {  
        Console.WriteLine("{0} Width: {1}, Height: {2}", s.GetType().Name,  
        s.Width, s.Height);  
        }  
    }  

    代码段Variance/ShapeDisplay.cs

    创建ShapeDisplay的一个新实例,会返回IDisplay<Shape>,并把它赋予shapeDisplay变量。因为IDisplay<T>是抗变的,所以可以把结果赋予IDisplay<Rectangle>,其中Rectangle派生自Shape。这次接口的方法只能把泛型类型定义为输入,而Rectangle满足Shape的所有要求:

    static void Main()  
    {  
    //...  
    IDisplay<Shape> shapeDisplay = new ShapeDisplay();  
    IDisplay<Rectangle> rectangleDisplay = shapeDisplay;  
    rectangleDisplay.Show(rectangles[0]);  
    }  

    代码段Variance/Program.cs

  • 相关阅读:
    php hash_hmac HMAC_SHA1 加密
    文件上传最好用绝对路径
    composer 安装插件
    php 账单生成
    php 求当前日期到下一年的实际天数
    php 求两个日期之间相差的天数
    PHP闭包 function() use()
    php 验证参数为0 但不为空
    php 原生mysqli
    前端框架选择
  • 原文地址:https://www.cnblogs.com/abc8023/p/3647721.html
Copyright © 2020-2023  润新知