(八)CSharp-泛型协变和逆变(3)

一、协变和逆变

可变性分为三种: 协变、逆变和不变。

协变和逆变:泛型接口泛型委托添加了一个处理类型转换问题的扩展。

问题: 当两个类对象是继承与派生的关系时,由于编译器通过泛型实例化时没法确认它们之间的关系,导致派生类对象不能赋值给基类对象。反之亦然,基类对象不能赋值给派生类对象。

解决: 当泛型接口或泛型委托实例化两个对象时(基类对象和派生类对象),通过协变和逆变的方式,来解决一个对象不能赋值给另一个对象的问题。

二、协变

非泛型类的代码例子:

class Animal
{public int NumberOfLegs = 4;}

class Dog:Animal{}

class Program
{
static void Main()
{
Animal a1 = new Animal();
Animal a2 = new Dog();
}

}

赋值兼容性

每一个变量都有一种类型,你可以将派生类型的对象赋值给基类型的变量,这叫作赋值兼容性。

请添加图片描述

但是对于泛型委托就不一样了,赋值原则虽成立,但是编译器会报错,因为派生类型委托没有从基类型委托派生。两者没有继承关系。

泛型委托错误的代码例子:

    class Animal { public int Legs = 4; }
    class Dog : Animal { }

    delegate T Factory<T>();

    class Program
    {
        static Dog MakeDog()
        {
            return new Dog();
        }
        static void Main(string[] args)
        {
            Factory<Dog> dogMaker = MakeDog;//创建委托对象
            Factory<Animal> animalMaker = dogMaker;//赋值不兼容,错误

            Console.WriteLine(animalMaker().Legs.ToString());
            Console.ReadKey();
        }
    }

两个委托对象是同级的,它们都是从 delegate 类型派生,后者又派生自 Object 类型。两者之间没有派生关系,因此赋值兼容性不适用。

在泛型委托实例化之前,编译器并不知道 T 类型中的Dog 类和 Animal 类是继承关系。

解决赋值原则不适用问题:对于所有这样的情况(上面错误例子的情况),我们可以使用由派生类创建的委托类型,这样应该能够正常工作,因为调用代码总是期望到一个基类的引用。

协变: 仅将派生类型用作输出值与构造委托有效性之间的常数关系叫作协变。

通过增加 out 关键字改变委托声明:

//把 delegate T Factory<T>();改为:
delegate T Factory<out T>();
//编译通过

增加了 out T 类型之后,类型变量 T 是 Animal 类。T 此时是知道的,所以知道 Dog 派生自 Animal。

Factory<out T>():T 类型变量为 Animal 类

三、逆变

逆变: 传入基类时允许传入派生对象的特性叫作逆变。

 class Animal { public int Legs = 4; }
    class Dog : Animal { }

    class Program
    {
        //in:逆变关键字
        delegate void Action1<in T>(T a);

        static void ActionAnimal(Animal a)
        {
            Console.WriteLine(a.Legs);
        }

        static void Main(string[] args)
        {
            Action1<Animal> act1 = ActionAnimal;
            Action1<Dog> dog1 = act1;

            dog1(new Dog());
            Console.ReadKey();
        }
    }
Action1<in T>(T a):T 类型变量是 Dog 类

四、协变和逆变的不同

请添加图片描述

五、接口的协变和逆变

协变和逆变可以应用到委托上,也可以适用于接口。

    class Animal { public string Name; }
    class Dog : Animal { }

    interface IMyIfc<out T>
    {
        T GetFirst();
    }

    class SimpleReturn<T>:IMyIfc<T>
    {
        public T[] items = new T[2];

        public T GetFirst()
        {
            return items[0];
        }
    }

    class Program
    {
        static void DoSomething(IMyIfc<Animal> returner)
        {
            Console.WriteLine(returner.GetFirst().Name);
        }
        static void Main(string[] args)
        {
            SimpleReturn<Dog> dogReturner = new SimpleReturn<Dog>();
            dogReturner.items[0] = new Dog() { Name = "Avonlea" };

            IMyIfc<Animal> animalReturner = dogReturner;
            DoSomething(animalReturner);

            Console.ReadKey();
        }
    }

六、关于可变性的更多内容

除了显式的协变和逆变,编译器还可以自动识别某个已构建的委托是协变还是逆变并且进行类型强制转换。(发生在没有为对象的类型赋值的时候)

    class Animal { public int Legs = 4; }
    class Dog : Animal { }

    class Program
    {
        delegate T Factory<out T>();

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

        static void Main(string[] args)
        {
            Factory<Animal> animalMaker1 = MakeDog;
            Factory<Dog> dogMaker = MakeDog;//隐式强制转换,不需要 out 也可以

            Factory<Animal> animalMaker2 = dogMaker; // 需要 out 标识符

            Factory<Animal> animalMaker3 = new Factory<Dog>(MakeDog);// 需要 out 标识符

            Console.ReadKey();
        }
    }

可变性的重要点:

  • 可变性处理的是可以使用基类型替换派生类型(或者派生类转换为基类)的安全情况。
  • 使用 in 和 out 关键字的显式变化只适用于委托和接口。
  • 不包括 in 和 out 关键字的委托和接口类型参数是不变的。
delegate T Factory<out R, in S, T>();
//out R:协变
//in S:逆变
//T:不变

猜你喜欢

转载自blog.csdn.net/chen1083376511/article/details/131153316