C#协变与逆变

C#协变与逆变

在学习C#过程中要灵活运用接口与委托那么协变与逆变是重中之重,本人开始学习时也处于懵懂状态,经过几天的查阅与思考才得出答案。

先来看一个例1

    class Animal
    {
        public int NumberOfLegs = 4;
    }


    class Dog : Animal
    {


    }


    class Program
    {
        static void Main(string[] args)
        {
            Animal a1 = new Animal();
            Animal a2 = new Dog();//将Dog类型隐式转化为Animal类型
            Console.WriteLine("Number of dog legs:" + a2.NumberOfLegs);
        }
    }

从上面这个例子中我们可以看到,基类是Animal,有一个Dog类从Animal类派生。在Main中,我们创建了一个Dog类型的对象,并且将他成功赋值给Animal类型对象的变量a2。所以说每一个变量都有一种类型,你可以将派生类对象的实例赋值给基类的变量,这叫做赋值兼容性

那么为什么可以将不同类型的引用转化呢?因为派生类包含了基类的信息,当派生类发现自己要转化和自己基类一个类型时,编译器知道这种做法是完全安全的,因为即将被转化的类型与自己基类型一样,所以不会造成基类里面的数据类型不匹配,所以编译器允许这样的类型转化。而反过来如果Animal类型要转化为Dog那么编译器会报错,这个就是因为如果基类要转化为与自己派生类一样的类型,那么必定会缺省派生类派生出来的那部分信息,这样是非常不安全的,编译器阻止这样做。总结一点,赋值兼容性实际是就是数据转化的安全性。

协变

先来看一个例2

     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 = MakeDog;//尝试赋值委托对象,转化成功
            Console.WriteLine(animalMaker().Legs);
        }
    }

先让dogMaker方法执行了返回Dog类型然后再把返回的Dog类型转化为Animal类型(Dog类型转换为Animal类型上面陈述了符合赋值兼容性),所以转化成功。

而另一个例3

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

首先说一点委托和类一样,是一种用户自定义类型。但类表示的是数据和方法的集合,而委托是持有一个或多个方法,以及一系列预定义操作,委托算是特殊的类。上面例子问题在于尽管Dog是Animal的派生类,但是委托Factory<Dog>没有从委托Factory<Animal>派生,两个委托对象是平级的(就是两个互不相干的类),所以赋值兼容性不适用。

那么怎样才可行呢?其实我们看到例2是能成功转换的,所以按照例2原理,加上out关键字即可(请看例4),这样就是强调编译器准备将返回值转化为目标值,这样委托的返回值与方法返回值就匹配了。其实本质上来讲就是根据的赋值兼容性。

例4

class Animal
    {
        public int Legs = 4;
    }


    class Dog : Animal
    {


    }


    delegate T Factory<out 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);
        }
    }

逆变

先来看看例5:

  class Animal
    {
    }

   class Dog : Animal
    {
    }

    class Program
    {
        delegate Animal Factory<T>();
        static Animal MakeAnimal()
        {
            return new Animal();
        }

        static void Main(string[] args)
        {
            Factory<Animal> animalMaker = MakeAnimal;
            Factory<Dog> dogmaker = animalMaker;//编译器报错,转换失败
        }
    }

这个例子有两点问题,第一:这两个委托对象之间是平级的,没有继承关系,不能转换。第二:你会说像协变一样我加一个out关键字来要求编译器转换委托方法的返回值,但是Dog是父类,而Animal是子类,要求子类转化为父类是不行的,上面提到过因为不安全,但是怎么样才能转换呢?其实你发现可以先让dogmaker委托方法返回值降一级,就是将派生类Dog转化为基类Animal,然后就与animalMaker对应起来了,这个顺序与协变相反,但是仍然是父类转化为子类,本质上满足赋值兼容性,所以我们要加in关键字要求编译器准备好这样的变换。这就是例6

    class Animal
    {
    }


    class Dog : Animal
    {
    }


    class Program
    {
        delegate Animal Factory<in T>();
        static Animal MakeAnimal()
        {
            return new Animal();
        }


        static void Main(string[] args)
        {
            Factory<Animal> animalMaker = MakeAnimal;
            Factory<Dog> dogmaker = animalMaker;//编译器正常,转换成功
        }
    }
协变与逆变就说到这里,要是我先学的是C++那么这些东西我理解得更快了=~=



  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值