18.协变逆变

18.协变逆变


18.1 知识点

协变逆变

协变:和谐的变化,自然的变化。因为里氏替换原则,父类可以装子类,所以子类变父类。比如 string 变成 object,感受是和谐的。

逆变:逆常规的变化,不正常的变化。因为里氏替换原则,父类可以装子类,但是子类不能装父类,所以父类变子类。比如 object 变成 string,感受是不和谐的。

协变和逆变是用来修饰泛型的。

  • 协变out
  • 逆变in

用于在泛型中修饰泛型字母,只有泛型接口和泛型委托能使用。

协变逆变的作用

class语句块外 namespace语句块内

返回值和参数

  1. out修饰的泛型委托只能作为返回值,不能作为参数。

    • delegate T TestOut1<out T>();//out修饰了T T只能作为返回值
      //delegate T TestOut2<out T>(T t);//报错 out修饰了T T不能作为参数
      
  2. in修饰的泛型委托只能作为参数,不能作为返回值。

    • delegate void TestIn1<in T>(T t);//in修饰了T T只能作为参数
      //delegate T TestIn2<in T>(T t);//报错 in修饰了T T不能作为返回值
      
  3. out修饰的泛型接口中,泛型类型只能作为返回值。

    • interface ITestOut<out T>
      {
          T TestFun1();//out修饰了T T只能作为返回值
          //void TestFun2(T t);//报错 out修饰了T T不能作为参数
      }
      
  4. in修饰的泛型接口中,泛型类型只能作为参数。

    • interface ITestIn<in T>
      {
          //T TestFun1();//报错 in修饰了T T不能作为返回值
          void TestFun2(T t);//in修饰了T T只能作为参数
      }
      
  5. 只有泛型接口和泛型委托能使用,泛型类、结构体等会报错。

    • //class Test<in T>//报错
      //{
      //}
      

结合里氏替换原则理解

// 定义父子类例子
// 父类
class Father
{

}
// 子类
class Son : Father
{

}

主函数内

协变:父类总是能被子类替换。

// 声明一个out修饰的泛型Son委托变量,装载一个返回Son的匿名函数
TestOut1<Son> outSon = () =>
{
    return new Son();
};

// 再声明一个out修饰的泛型Father委托变量
// outFather委托变量可以装载outSon委托变量
TestOut1<Father> outFather = outSon;
Father father = outFather();
// 实际上,执行outFather 返回的是 outSon里面装的函数 返回的是Son
// Father类型的变量father 是用 父类Father 装 子类Son

逆变:父类总是能被子类替换。

// 声明一个in修饰的泛型Father委托变量,装载一个参数是Father的匿名函数
TestIn1<Father> inFather = (value) =>
{

};

// 再声明一个in修饰的泛型Son委托变量
// inSon委托变量可以装载inFather委托变量
TestIn1<Son> inSon = inFather;
inSon(new Son());
// 实际上,执行inSon时,实际执行的是inFather
// 执行inSon时,传入Son类型的参数
// 因为实际执行的是inFather
// 正常来说执行inFather时,要传入的是Father类型的参数
// 但是现在传入的是Son类型的参数
// 其实还是 参数类型Father 装 参数类型Son,父类装子类

总结

  • 协变out
  • 逆变in
  • 用来修饰泛型替代符,只能修饰接口和委托中的泛型。

作用

  1. out修饰的泛型类型只能作为返回值类型,in修饰的泛型类型只能作为参数类型。
  2. 遵循里氏替换原则的,用outin修饰的泛型委托可以相互装载(有父子关系的泛型)。
    • 协变:父类泛型委托装子类泛型委托

    • 逆变:子类泛型委托装父类泛型委托


18.2 知识点代码

using System;

namespace Lesson17_协变逆变
{
    #region 知识点一 什么是协变逆变

    //协变:和谐的变化,自然的变化
    //因为 里氏替换原则 父类可以装子类
    //所以 子类变父类  
    //比如 string 变成 object 
    //感受是和谐的

    //逆变:逆常规的变化,不正常的变化
    //因为 里氏替换原则 父类可以装子类 但是子类不能装父类
    //所以 父类变子类  
    //比如 object 变成 string
    //感受是不和谐的

    //协变和逆变是用来修饰泛型的
    //协变:out 
    //逆变:in
    //用于在泛型中 修饰 泛型字母的 
    //只有泛型接口和泛型委托能使用

    #endregion

    //class语句块外 namespace语句块内

    #region 知识点二 协变逆变的作用

    //1.返回值 和 参数

    //用out修饰的泛型委托 只能作为返回值 不能作为参数
    delegate T TestOut1<out T>();//out修饰了T T只能作为返回值
    //delegate T TestOut2<out T>(T t);//报错 out修饰了T T不能作为参数

    //用in修饰的泛型委托 只能作为参数 不能作为返回值
    delegate void TestIn1<in T>(T t);//in修饰了T T只能作为参数
    //delegate T TestIn2<in T>(T t);//报错 in修饰了T T不能作为返回值

    //用out修饰的泛型接口
    interface ITestOut<out T>
    {
        T TestFun1();//out修饰了T T只能作为返回值
        //void TestFun2(T t);//报错 out修饰了T T不能作为参数
    }

    //用in修饰的泛型接口
    interface ITestIn<in T>
    {
        //T TestFun1();//报错 in修饰了T T不能作为返回值
        void TestFun2(T t);//in修饰了T T只能作为参数
    }

    //只有泛型接口和泛型委托能使用 泛型类 结构体等 会报错
    //class Test<in T>//报错
    //{

    //}



    //2.结合里氏替换原则理解

    //父类
    class Father
    {

    }
    //子类
    class Son : Father
    {

    }

    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("协变逆变");

            //主函数内

            #region 知识点二 协变逆变的作用(结合里氏替换原则理解)

            //协变 父类总是能被子类替换
            //看起来 就是 son ——> father

            //声明一个out修饰的泛型Son委托变量 装载一个返回Son的匿名函数
            TestOut1<Son> outSon = () =>
            {
                return new Son();
            };

            //再声明一个out修饰的泛型Father委托变量
            //正常来说 假如TestOut1不是out修饰的泛型委托的话
            //outFather委托变量 不能装 outSon委托变量 会报错
            //但是 TestOut1加了out修饰的泛型 可以用 outFather 装 outSon
            TestOut1<Father> outFather = outSon;
            Father father = outFather();
            //实际上 执行outFather 返回的是 outSon里面装的函数 返回的是Son
            //Father类型的变量father 是用 父类Father 装 子类Son



            //逆变 父类总是能被子类替换
            //看起来像是 father——>son 明明是传父类 但是你传子类 不和谐的

            //声明一个in修饰的泛型Father委托变量 装载一个参数是Father的匿名函数
            TestIn1<Father> inFather = (value) =>
            {

            };

            //再声明一个in修饰的泛型Son委托变量
            //正常来说 假如TestIn1不是in修饰的泛型委托的话
            //inSon委托变量 不能装 inFather委托变量 会报错
            //但是 TestIn1加了in修饰的泛型 可以用 inSon 装 inFather
            TestIn1<Son> inSon = inFather;
            inSon(new Son());
            //实际上 执行inSon时 实际执行的是 inFather
            //执行inSon时  传入Son类型的参数
            //因为实际执行的是inFather
            //正常来说执行inFather时 要传入的是Father类型的参数
            //但是现在 传入的是Son类型的参数
            //其实还是 参数类型Father 装 参数类型Son 父类装子类

            #endregion
        }
    }

    //总结

    //协变 out
    //逆变 in
    //用来修饰 泛型替代符的  只能修饰接口和委托中的泛型

    //作用
    //1.out修饰的泛型类型 只能作为返回值类型 in修饰的泛型类型 只能作为 参数类型
    //2.遵循里氏替换原则的  用out和in修饰的 泛型委托 可以相互装载(有父子关系的泛型)
    //  协变  父类泛型委托装子类泛型委托    逆变 子类泛型委托装父类泛型委托
}

18.3 练习题

请描述协变逆变有什么作用

  • 协变和逆变是用来修饰泛型替代符的,在泛型委托和泛型接口中起作用。
  • 使用 out 修饰的泛型类型只能作为返回值类型,称为协变;而使用 in 修饰的泛型类型只能作为参数类型,称为逆变。
  • 遵循里氏替换原则,使用 outin 修饰的泛型委托如果类型是父子关系,则可以相互装载:
    • 协变:父类泛型委托容器可以装载子类泛型委托容器。
    • 逆变:子类泛型委托容器可以装载父类泛型委托容器。

通过代码说明协变和逆变的作用

class语句块外 namespace语句块内

// 协变
delegate T TestOut<out T>();

// 逆变
delegate void TestIn<in T>(T v);

class Father
{

}

class Son : Father
{

}

主函数内

// 协变 代码
TestOut<Son> testOutSon = () =>
{
    return new Son();
};
TestOut<Father> testOutFather = testOutSon;
Father father = testOutFather();

// 逆变 代码
TestIn<Father> testInFather = (value) =>
{

};
TestIn<Son> testInSon = testInFather;
testInSon(new Son());

18.4 练习题代码

using System;

namespace Lesson17_练习题
{
    //class语句块外 namespace语句块内

    #region 练习题二

    //协变
    delegate T TestOut<out T>();

    //逆变
    delegate void TestIn<in T>(T v);

    class Father
    {

    }

    class Son : Father
    {

    }

    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("协变逆变练习题");

            #region 练习题一
            //请描述协变逆变有什么作用

            //是用来修饰泛型替代符的  泛型委托和泛型接口中
            //1.out修饰的泛型类型 只能作为返回值类型 协变
            //  in修饰的泛型类型 只能作为参数类型   逆变

            //2.遵循里氏替换原则 用out和in修饰的泛型委托 如果类型是父子关系 那么可以相互装载
            // 协变: 父类泛型委托容器可以装 子类泛型委托容器 
            // 逆变: 子类泛型委托容器可以装 父类泛型委托容器
            #endregion

            //主函数内

            #region 练习题二
            //通过代码说明协变和逆变的作用

            //协变 代码
            TestOut<Son> testOutSon = () =>
            {
                return new Son();
            };
            TestOut<Father> testOutFather = testOutSon;
            Father father = testOutFather();

            //逆变 代码
            TestIn<Father> testInFather = (value) =>
            {

            };
            TestIn<Son> testInSon = testInFather;
            testInSon(new Son());

            #endregion
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏