C#进阶 - 逆变与协变

举报
Damon小智 发表于 2024/05/29 10:10:24 2024/05/29
【摘要】 在C#中,子类可以隐式转换为父类。这种子类到父类的转换称为协变。而类似于父类转向子类的变换,可以简单理解为逆变。逆变和协变可以用于泛型委托和泛型接口,本篇文章将讲解C#中逆变和协变的使用。逆变和协变的语法初次接触可能会感到陌生,但通过在项目中多实践,相信会有很多感悟。

协变与逆变

  • 协变(共变):泛型委托或泛型接口的类型转换类似于父类转向子类的变换。
  • 逆变(反变):泛型委托或泛型接口的类型转换类似于子类到父类的隐式转换。

逆变与协变用来描述类型转换后的继承关系,其定义如下:

  • 如果A、B表示类型,f(x)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类)
  • 当A≤B时,若f(x)是逆变的,则f(B)≤f(A)成立;
  • 当A≤B时,若f(x)是协变的,则f(A)≤f(B)成立。

举个例子

stringobject的子类,但List<string>List<object>没有继承关系。如果想实现List<string>List<object>的继承关系,就需要使用逆变和协变。令List<string>成为List<object>子类的变化,叫做协变,和stringobject的父子关系相同;反之,令List<object>成为List<string>子类的变化,叫做逆变,和stringobject的父子关系相反。

协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。

演示代码

以下是演示逆变和协变的代码示例:

class Program
{
    /* 泛型委托 */
    public delegate T DelegateFuncA<out T>(); // 支持协变
    public delegate void DelegateFuncB<in T>(T param); // 支持逆变

    /* 泛型接口 */
    public interface InterfaceFuncA<out T> { T Get(); } // 支持协变
    public interface InterfaceFuncB<in T> { void Set(T param); } // 支持逆变

    static void Main(string[] args)
    {
        // 泛型委托 - 协变
        /* object父类变成string子类 */
        DelegateFuncA<object> funcObject = null;
        DelegateFuncA<string> funcString = null;
        funcObject = funcString; // 协变

        // 泛型委托 - 逆变
        /* string子类变成object父类 */
        DelegateFuncB<object> actionObject = null;
        DelegateFuncB<string> actionString = null;
        actionString = actionObject; // 逆变

        // 泛型接口 - 协变
        InterfaceFuncA<object> interfaceFuncObject = null;
        InterfaceFuncA<string> interfaceFuncString = null;
        InterfaceFuncObject = interfaceFuncString; // 协变

        // 泛型接口 - 逆变
        InterfaceFuncB<object> interfaceFuncObjectB = null;
        InterfaceFuncB<string> interfaceFuncStringB = null;
        interfaceFuncStringB = interfaceFuncObjectB; // 逆变

        // 数组 - 协变
        // 数组的协变使派生程度更大的类型的数组能够隐式转换为派生程度更小的类型的数组。
        object[] array = new string[10];
        // array[0] = 10; // 运行时错误:无法将int分配给string数组
    }
}

解释代码

  • 泛型委托

    • DelegateFuncA<out T>:声明了一个支持协变的泛型委托。
    • DelegateFuncB<in T>:声明了一个支持逆变的泛型委托。
  • 泛型接口

    • InterfaceFuncA<out T>:声明了一个支持协变的泛型接口。
    • InterfaceFuncB<in T>:声明了一个支持逆变的泛型接口。
  • 泛型委托的协变

    • DelegateFuncA<object> funcObject = null;
    • DelegateFuncA<string> funcString = null;
    • funcObject = funcString;:通过协变,funcString可以赋值给funcObject
  • 泛型委托的逆变

    • DelegateFuncB<object> actionObject = null;
    • DelegateFuncB<string> actionString = null;
    • actionString = actionObject;:通过逆变,actionObject可以赋值给actionString
  • 泛型接口的协变

    • InterfaceFuncA<object> interfaceFuncObject = null;
    • InterfaceFuncA<string> interfaceFuncString = null;
    • interfaceFuncObject = interfaceFuncString;:通过协变,interfaceFuncString可以赋值给interfaceFuncObject
  • 泛型接口的逆变

    • InterfaceFuncB<object> interfaceFuncObjectB = null;
    • InterfaceFuncB<string> interfaceFuncStringB = null;
    • interfaceFuncStringB = interfaceFuncObjectB;:通过逆变,interfaceFuncObjectB可以赋值给interfaceFuncStringB
  • 数组的协变

    • object[] array = new string[10];:数组的协变使string数组能够隐式转换为object数组。

总结

通过上述示例,我们展示了如何在C#中使用逆变和协变。逆变和协变为泛型委托和泛型接口提供了更大的灵活性,使得不同类型之间可以进行更方便的转换。在实际开发中,多多使用这些特性,可以更好地理解和应用它们,提升代码的灵活性和可维护性。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。