zoukankan      html  css  js  c++  java
  • Effective C# 学习笔记(二十九)在范型中的协变和逆变

    协变:若一个返回值类型可以用该类型的子类型(继承类型)替代,我们就说该类型是可协变的

    逆变:若一个参数的类型可以用该类型的父类型(被继承类型)替代,我们就说该类型是被逆变的。

    在你的范型声明、使用中,注意支持协变(Covariance)和逆变(Contravariance)。编译器会捕捉你使用不当的逆变或协变行为。

    举例说明协变和逆变:

    这里有个抽象类CelestialBody,有Planet、Moon、Asteroid三个类型继承自该基类:

    abstract public class CelestialBody

    {

    public double Mass { get; set; }

    public string Name { get; set; }

    // elided

    }

    public class Planet : CelestialBody

    {

    // elided

    }

    public class Moon : CelestialBody

    {

    // elided

    }

    public class Asteroid : CelestialBody

    {

    // elided

    }

    //下面这个方法以协变的方式来处理CeletialBody数组

    public static void CoVariantArray(CelestialBody[] baseItems)

    {

    foreach (var thing in baseItems)

    Console.WriteLine("{0} has a mass of {1} Kg",

    thing.Name, thing.Mass);

    }

    //但下面这两种赋值操作是不安全的,编译可通过,但在运行时会抛出异常System.ArrayTypeMismatchException: 尝试访问类型与数组不兼容的元

    素。

    public static void UnsafeVariantArray(

    CelestialBody[] baseItems)

    {

    baseItems[0] = new Asteroid { Name = "Hygiea", Mass = 8.85e19 };

    }

    CelestialBody[] spaceJunk = new Asteroid[5];

    spaceJunk[0] = new Planet();

     

    当范型这个特性引入C#中时,其对协变逆变的支持的很苛刻的。直到C# 4.0版本才添加了新的关键字(协变out  和逆变 in)来修饰范型接口的类型参数,才使这些范型可以更好的支持协变、逆变。

    让我们看一个例子:

    public static void CoVariantGeneric(

    IEnumerable<CelestialBody> baseItems)

    {

    foreach (var thing in baseItems)

    Console.WriteLine("{0} has a mass of {1} Kg",

    thing.Name, thing.Mass);

    }

    上面这段代码用IEnumerable<T>作为形参类型,其可以用List<Planet>类型的对象作为参数。这是因为IEnumerable<T>利用了out关键字来限制T类型的使用范围。即类型IEnumberable<T>类型只能被用来作为返回值、属性的get访问器即部分代理的参数类型。IEnumberable<T>定义代码如下:

    public interface IEnumerable<out T> : IEnumerable

    {

    IEnumerator<T> GetEnumerator();

    }

    public interface IEnumerator<out T> :

    IDisposable, IEnumerator

    {

    T Current { get; }

    // MoveNext(), Reset() inherited from IEnumerator

    }

     

    适用范围:

    协变:方法的返回值的类型,属性的get访问器的类型、部分代理中的参数类型

    逆变:方法参数的类型,属性的set访问器的类型,部分代理中的参数类型

    之前说的部分代理中的参数类型,其规范由BCL定义如下(这里只列出了部分重载):

    public delegate TResult Func<out TResult>();

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

    public delegate TResult Func<in T1, T2, out TResult>(T1 arg1,T2 arg2);

    public delegate void Action<in T>(T arg);

    public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);

    public delegate void Action<in T1, in T2, T3>(T1 arg1,T2 arg2, T3 arg3);

    另外注意协变、逆变的声明上下文:

    //协变接口

      public interface ICovariantDelegates<out T>

      {

            T GetAnItem();

            Func<T> GetAnItemLater();

            void GiveAnItemLater(Action<T> whatToDo);

      }


        在ICovariantDelegates<out T>接口中,T类型被声明为协变类型,而在该接口的

    GiveAnItemLater方法中 Action<T>作为了该方法的参数,此时的T应为in 修饰的逆变类型参数,但这里的T是和上下文相关的,它只对ICovariantDelegates接口负责,也就是说尽管Action<in T>被声明为逆变的,T在GiveAnItemLater中的Action<T>声明是合法的。下例中的ActOnAnItemLater也是如此。

     //逆变接口

      public interface IContravariantDelegates<in T>

      {

            void ActOnAnItem(T item);

            void GetAnItemLater(Func<T> item);

            Action<T> ActOnAnItemLater();

      }

  • 相关阅读:
    python之路_爬虫之selenium模块
    python之路_爬虫之requests模块补充
    扩展中国剩余定理讲解
    扩展中国剩余定理讲解
    bzoj3225 [Sdoi2008]立方体覆盖——扫描线
    差分约束讲解
    CF917C Pollywog —— 状压DP + 矩乘优化
    斜率优化讲解
    AC自动机讲解
    BZOJ2870—最长道路tree
  • 原文地址:https://www.cnblogs.com/haokaibo/p/2108789.html
Copyright © 2011-2022 走看看