前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >认真CS☀️协变、逆变 & 不变

认真CS☀️协变、逆变 & 不变

作者头像
星河造梦坊官方
发布2024-08-14 16:46:24
980
发布2024-08-14 16:46:24
举报
文章被收录于专栏:星河造梦坊专栏

赋值兼容性:你可以将派生类对象的实例赋值给基类的变量,这叫做赋值兼容性

代码语言:javascript
复制
class Animal { }
class dog : Animal { }

class Program
{
    static void Main()
    {
        Animal a = new Animal();
        Animal b = new dog();
    }
}

🟥 协变

out关键字指明类型参数是协变的

上面这段代码,dog是派生自Animal类,它是可以直接赋值给Animal类的,但此代码却产生错误,这是因为委托也是类型,Factory<Dog>和Factory<Animal>都派生自delegate,他们是平级关系,不是父子关系,自然他们定义的变量无法相互赋值,即使它们的变量引用的对象是父子关系,可以赋值的,它们的变量也不可以赋值

1、我们不将dog赋值给animal(dog是Factory<Dog>类了,无法赋值给同级别的Factory<Animal>类),而是直接将它的引用MakeDog赋值给animal,这是可行的

2、我们还有一种方法,那就是协变(在类型参数前加out)(派生类只是用来输出值)

“协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。

“协变”->”和谐的变”->”很自然的变化”->string->object :协变

协变在委托声明中加入out关键字,表示派生类只是用来输出值,避免出现由delegate派生类是平级,它们定义的变量无法相互赋值产生的问题

代码语言:javascript
复制
using System;

namespace 可变性
{
    class Animal { }
    class Dog:Animal { }

    delegate T Factory<out T>();

    class Program
    {
        static Dog MakeDog()
        {
            return new Dog();
        }

        static void Main()
        {
            Factory<Dog> dog = MakeDog;
            Factory<Animal> animal = dog;
        }
    }
}

🟧 逆变

in关键字指明类型参数是逆变的

逆变:在类型参数前加in

基类对象的引用期望的是传入到基类对象,但实际上(也允许它)传入到派生对象,这叫做逆变

这样可以工作,因为在调用的时候,调用代码传入了派生类型的变量,方法期望的只是其基类,方法完全可以像以前那样操作对象的基类部分

“逆变”则是指能够使用派生程度更小的类型。

“逆变”->”逆常的变”->”不正常的变化”->object->string 逆变。

代码语言:javascript
复制
using System;

class Animal
{
    public int NumberOfLegs = 4;
}

class Dog : Animal { }

class Program
{
    delegate void Action<in T>(T a);

    static void ActOnAnimal(Animal a) { Console.WriteLine(a.NumberOfLegs); }

    static void Main()
    {
        Action<Animal> act = ActOnAnimal;
        Action<Dog> dog=act;
        dog(new Dog());
    }
} 

🟨 协变和逆变的不同

协变(out)是将派生类对象的引用传入到基类对象,输出派生类的值

逆变(in)是将基类对象的引用传入到派生对象,派生对象只能操作基类部分

🟩 接口的协变和逆变

1️⃣ 接口的协变

代码语言:javascript
复制
using System;

class Father { }
class Son : Father { }

interface IMyIfc<out T> { }

class Example<T> : IMyIfc<T> { }    //它是接口实现类

class Program
{
    static void DoSomething(IMyIfc<Father> a) { }

    static void Main()
    {
        Example<Son> son = new Example<Son>();
        IMyIfc<Father> father = son;   //接口协变

        DoSomething(father);
    }
}

2️⃣ 接口的逆变

代码语言:javascript
复制
using System;

class Father { }
class Son : Father { }

interface IMyIfc<in T> { }

class Example<T> : IMyIfc<T> { }    //它是接口实现类

class Program
{
    static void DoSomething(IMyIfc<Father> a) { }

    static void Main()
    {
        Example<Father> father = new Example<Father>();
        IMyIfc<Son> son = father;   //接口逆变

        DoSomething(father);
    }
}

3️⃣ 协变和逆变的隐式强制转换

编译器自动识别某个已构建的委托是协变或是逆变并且自动进行强制转换

代码语言:javascript
复制
using System;

class Father { public int a = 10; }
class Son : Father { }

class Program
{

    delegate T Factory<out T>();

    static Son DoSomething() { return new Son(); }

    static void Main()
    {
        Factory<Father> father = DoSomething;  //协变隐式强制转换
        Console.WriteLine(father().a);
    }
}

🟦 有关可变性的其他重要注意事项:

a、变化处理的是使用派生类替换基类的安全情况,反之亦然。因此变化只是用于引用类型,不能从值类型派生其他类型

b、显式变化使用in和out关键字只适用于委托和接口,不适用于类、结构和方法

c、不包括in、out关键字的委托和接口类型参数叫不变。这些类型参数不能用于协变或逆变

代码语言:javascript
复制
delegate T Factory< out R,  in S,  T >();
//                  协变    逆变  不变

大家还有什么问题,欢迎在下方留言!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🟥 协变
  • 🟧 逆变
  • 🟨 协变和逆变的不同
  • 🟩 接口的协变和逆变
    • 1️⃣ 接口的协变
      • 2️⃣ 接口的逆变
        • 3️⃣ 协变和逆变的隐式强制转换
        • 🟦 有关可变性的其他重要注意事项:
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档