认真CS丨协变、逆变 & 不变

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

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派生类是平级,它们定义的变量无法相互赋值产生的问题

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 逆变。 

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)是将基类对象的引用传入到派生对象,派生对象只能操作基类部分


接口的协变和逆变

接口的协变

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);
    }
}


接口的逆变

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);
    }
}


协变和逆变的隐式强制转换

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

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关键字的委托和接口类型参数叫不变。这些类型参数不能用于协变或逆变

delegate T Factory< out R,  in S,  T >();
//                  协变    逆变  不变

猜你喜欢

转载自blog.csdn.net/weixin_38239050/article/details/80217420