zoukankan      html  css  js  c++  java
  • C# -- 泛型(3)

    简介:

      前两篇文章讲了关于泛型的一些基础,下面笔者通过这篇文章来给刚刚接触泛型的朋友介绍一下

      <1>.原理性的东西----” 泛型的协变和逆变 “

      <2>.以及常用的接口----” IEnumerable 及其泛型版的IEnumerable<out T> “

    ------------------------------------------------------------------------------------------------------------------------------------------------------------

     

     

    <泛型的协变逆变|泛型修饰符‘out’与‘in’>

    |首先这2个拗口的名词先不用去管它,先知道协变和逆变主要是用在泛型的接口委托上就可以了,下面我们通过一个例子来看看:

    |在这之前我们插点别的东西,我们知道接口是可以体现多态的,当然接口体现的多态注重功能上的多态,这和抽象类不同,抽象类更注重的是建立在血缘关系上的多态

    知道接口是可以体现多态的之后,我们来看看一个相关的例子--

    鸟和飞机都会飞,把飞定义成一个借口,在定义2个类

        public interface IFlyable
        {
            void fly();
        }
        class Bird:IFlyable
        {
            public void fly()
            {
                Console.WriteLine("鸟儿飞!");
            }
        }
        class Plane:IFlyable
        {
            public void fly()
            {
                Console.WriteLine("飞机飞!");
            }
        }

     

    下面看看接口体现的多态性:

                IFlyable ifly; 
                
                ifly = new Bird();
                ifly.fly();
    
                ifly = new Plane();
                ifly.fly();

    运行结果:

    鸟儿飞!

    飞机飞!

     

    了解了接口的多态性后我们再来看一个例子:

    这里定义了2个类 Animal 和 Cat (Cat继承了Animal)

        public class Animal
        {
        }
    
        public class Cat:Animal
        {
        }

     

    继续往下看:

    Cat cat = new Cat();

     

    下面这句代码,cat向animal转,子类向父类转换,这时cat会隐式转换为animal 我们说“儿子像父亲” 这是完全可以理解的

    Animal animal = cat;

     

    但是 说”父亲像儿子“ 这是说不过去的 ,但是有的时候如果儿子坑爹强制转换了一下还是可以的

    cat = (Cat)animal;

     

     

    (协变)

                List<Cat> catArray = new List<Cat>();
                List<Animal> animalArray = catArray;

     

    如果是上面说的类,这样写是可以的,但是这里是会报错的  如图

    继续往下看 这样写却可以

                IEnumerable<Cat> lCat = new List<Cat>();
                IEnumerable<Animal> lAnimal = lCat;

     

    对 IEnumerable<Cat> 转到定义 如图 我们发现这里多了一个 “out” 关键字

    概念引入

    1.对于泛型类型参数,out 关键字指定该类型参数是协变的。 可以在泛型接口和委托中使用 out 关键字。“协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。

    --对于 “协变” 笔者是这样理解的就是”说的通变化“ 就像 “儿子像父亲一样”(假定父亲派生程度0那么儿子的派生程度就是1了,所以父亲可以使用派生程度更大的儿子)

    协变与多态性类似,因此它看起来非常自然。

     

    (逆变)

    我们知道IComparable<T>接口中,T的修饰符是‘in’,下面我们修改一下上面的代码演示一下 

     

        class Cat : Animal, IComparable<Cat>
        {
            //仅演示
            public int CompareTo(Cat other)
            {
                return 1;
            }
        }
    
        class Animal : IComparable<Animal>
        {
            //仅演示
            public int CompareTo(Animal other)
            {
                return 1;
            }
        }

     

     

    这里Cat和Animal都实现了IComparable<T>接口,然后我们这样写

                IComparable<Cat> ICat = new Cat();
                IComparable<Animal> IAnimal = new Animal();
                ICat = IAnimal;

     

    代码中ICat(高派生程度)使用 IAnimal(低派生程度) “父亲像儿子” 和上面的例子完全相反。

     

    概念引入:

    2.对于泛型类型参数,in 关键字指定该类型参数是逆变的。 可以在泛型接口和委托中使用 in 关键字。“逆变”则是指能够使用派生程度更小的类型。

    --对于 “逆变” 笔者的理解则是 “坑爹儿子” 反过来硬说 “父亲像儿子” 这是 “说不过去的” 只是利用了强硬的手段

     

     

    在了解了上面的内容后,我们来看看“out” 与 “in” 关键字的特性

    IEnumerable<T>接口的IEnumerator<T> GetEnumerator()方法返回了一个迭代器 ,不难发现T如果用 out 标记,则T代表了输出,也就说只能作为结果返回。

    IComparable<T>接口的CompareTo(T other)方法传入了一个T类型的Other参数,不难发现T如果用 in 标记,则T代表了输入,也就是它只能作为参数传入。

    下面我们演示一个例子

    将动物会叫这功能,定义成一个泛型借口用 out 修饰

    这里会出现一个错误

    把第二个带参数的setSound方法,去掉后编译可以正常通过

    下面我们把 out 改成 in

    这里会出现一个错误

    把第一个setSound方法,去掉后编译可以正常通过,或者把第一个方法的返回值,改成其它非T类型,编译也可通过

    这个演示充分说明了:out 修饰 T 则 T只能作为结果输出而不能作为参数  ; in 修饰 T 则 T只能作为参数而不能作为结果返回;

     

    ------------------------------------------------------------------------------------------------------------------------------------------------------------

     

     

    <IEnumerable接口及其泛型版>

     为什么要用IEnumerable接口? 下面我们通过一个例子看看:

        //定义Person类
        public class Person
        {
            public Person(string _name)
            {
                this.name = _name;
            }
    
            public string name;
        }
    
        //定义People类
        public class People
        {
            private Person[] _people;
    
            public People(Person[] pArray)
            {
                //实例化数组 用于存Person实例
                _people = new Person[pArray.Length];
    
                for (int i = 0; i < pArray.Length; i++)
                {
                    _people[i] = pArray[i];
                }
            }
        }

    上面的代码我们定义了一个 Person 类和一个 People 类,显然 People是用来存放多个Person实例的集合,下面我们尝试用 Foreeach 遍历集合的每个元素 输出:

            static void Main(string[] args)
            {
                Person[] personArray = new Person[3]{
                new Person("Keiling1"),
                new Person("Keiling2"),
                new Person("Keiling3"),
                };
    
                People people = new People(personArray);
                foreach (Person item in people)
                {
                    Console.WriteLine(item.name);
                }
            }

    这里编译不能通过,出现了一个错误

    GetEnumerator:是IEnumerable接口中的一个方法,它返回一个 IEnumerator(迭代器),如下图 

    IEnumerator内部规定了,实现一个迭代器的所有基本方法,包括 如下图

    为了在foreach中使用 People的实例, 我们给People实现IEnumerable接口,代码如下:

        public class People:IEnumerable
        {
            private Person[] _people;
    
            public People(Person[] pArray)
            {
                //实例化数组 用于存Person实例
                _people = new Person[pArray.Length];
    
                for (int i = 0; i < pArray.Length; i++)
                {
                    _people[i] = pArray[i];
                }
            }
    
    ////IEnumerable和IEnumerator通过IEnumerable的GetEnumerator()方法建立了连接,可以通过IEnumerable的GetEnumerator()得到IEnumerator对象。
            IEnumerator IEnumerable.GetEnumerator()
            {
                return (IEnumerator)GetEnumerator();
            }
    
            public PeopleEnum GetEnumerator()
            {
                return new PeopleEnum(_people);
            }
        }
    
        public class PeopleEnum:IEnumerator
        {
            public Person[] _people;
    
            public PeopleEnum(Person [] pArray)
            {
                _people = pArray;
            }
            //游标
            int position = -1;
    
            //是否可以往下 移
            public bool MoveNext()
            {
                position++;
                return (position < _people.Length);
            }
    
            //集合的所有元素取完了之后 重置position
            public void Reset()
            {
                position = -1;
            }
    
            //实现 IEnumerator的 Current方法 返回当前所指的Person对象
            object IEnumerator.Current
            {
                get
                {
                    return Current;
                }
            }
    
            //Current是返回Person类实例的只读方法
            public Person Current
            {
                get
                {
                    try
                    {
                        return _people[position];
                    }
                    catch (IndexOutOfRangeException)
                    {
                        throw new InvalidOperationException();
                    }
                }
            }
        }

    测试运行:

            static void Main(string[] args)
            {
                Person[] personArray = new Person[3]{
                new Person("Keiling1"),
                new Person("Keiling2"),
                new Person("Keiling3"),
                };
    
                People people = new People(personArray);
                foreach (Person item in people)
                {
                    Console.WriteLine(item.name);
                }
            }

    结果:

    总结:

    1.一个集合要支持foreach方式的遍历,必须实现IEnumerable接口,描述这类实现了该接口的对象,我们叫它 ‘序列’。

    比如 List<T> 支持 foreach 遍历 是因为它实现了IEnumerable接口和其泛型版,如图--

     

     

    2. IEnumerator对象具体实现了迭代器(通过MoveNext(),Reset(),Current)。

     

    3. 从这两个接口的用词选择上,也可以看出其不同:IEnumerable是一个声明式的接口,声明实现该接口的class是“可枚举(enumerable)”的,但并没有说明如何实现迭代器,

    而IEnumerator是一个实现式的接口,IEnumerator对象就是一个迭代器。 

     

    关于IEnumerable<T>我们来了解一下它的代码:

     

    4.由于IEnumerable<T>继承了IEnumerable接口,所以要实现IEnumerator<T> ,还需要实现IEnumerator接口,由于和泛型版本的方法同名,所以该方法的实现需要使用显式接口实现。这里就不继续介绍它的具体实现了,和IEnumerator基本一致,这里就不详述了,读者可以自己动手写一下。
     
    ps 了解IEnumerable和IEnumerable<T>对今后学西理解LINQ是有很大帮助的。

     

    出自: Keiling_J'Blog http://www.cnblogs.com/keiling/

     

     

     

     

  • 相关阅读:
    HDU 5115 Dire Wolf (区间DP)
    HDU 4283 You Are the One(区间DP(最优出栈顺序))
    ZOJ 3469 Food Delivery(区间DP好题)
    LightOJ 1422 Halloween Costumes(区间DP)
    POJ 1651 Multiplication Puzzle(区间DP)
    NYOJ 石子合并(一)(区间DP)
    POJ 2955 Brackets(括号匹配一)
    POJ 1141 Brackets Sequence(括号匹配二)
    ZOJ 3537 Cake(凸包+区间DP)
    Graham求凸包模板
  • 原文地址:https://www.cnblogs.com/keiling/p/3687638.html
Copyright © 2011-2022 走看看