• 20.C# 创建自己的泛型类型


    1.定义泛型类

    可以使用以下语法创建泛型类,T可以是任意符合C#标识符命名规范的任意标识符

    class MyGenericClass<T>
    {
     //....   
    }

    泛型类可以包含任意多个类型,使用逗号隔开。定义了这些类型之后就可以像其他类型一样使用它们,比如用作成员变量的类型,属性或方法的返回值,方法的参数类型等等。如下把T1用作成员变量的类型、属性的返回值,方法的参数类型。

        class MyGenericClass<T1, T2, T3>
        {
            private T1 innerT1Object;
    
            public MyGenericClass()
            { 
                
            }
    
            public MyGenericClass(T1 t)
            {
                innerT1Object = t;
            }
    
            public T1 InnerT1Object
            {
                get { return innerT1Object; }
            }
    
        }

    注:不能使用定义的类型T来创建它的对象,因为T可能是抽象类,或者没有公共的构造函数。我们唯一可以假设的是T是继承于System.Object的类型,只能使用Object类提供的方法。

    如下代码是不能编译的

        class MyGenericClass<T1, T2, T3>
        {
            private T1 innerT1Object;
    
            public MyGenericClass()
            {
                innerT1Object = new T1(); 
            }
    
            public MyGenericClass(T1 t)
            {
                innerT1Object = t;
            }
    
            public T1 InnerT1Object
            {
                get { return innerT1Object; }
            }
    
        }

    也不能用+、-、==、!=等运算符比较两个T对象,因为该对象可能不支持这个运算符,但可以用==和!==比较T对象和null

            public bool Compare(T1 op1,T2 op2)
            {
                if (op1 != null && op2 != null)//正确
                    return true;
                else
                    return false;
            }
    
            public bool Compare(T1 op1, T2 op2)
            {
                if (op1 == op2)//错误
                    return true;
                else
                    return false;
            }

    1.1Default关键字

    因为不知道T类型是什么类型,所以就不好为T类型的变量赋值,但可以使用default为T类型的变量赋值,它可以根据T的具体类型赋予默认值,比如引用类型赋值null,int赋予0

            public MyGenericClass()
            {
                innerT1Object = default(T1);
            }

    1.2约束

    前面定义的泛型类型称为无绑定类型,因为没有对它们进行任何约束。可以使用约束把T类型限制为某个类或者继承于某个具体的类。

    使用where 关键字约束泛型,可以有多个约束,多个约束直接用逗号隔开,也可以有多个where用于约束不同的类型,where约束必须写在继承的类或接口后面

    class MyGenericClass<T1,T2>:IMyInterface where T1:Constraint1,Constraint2 where T2:Constraint3

    下表列出了一些可用的约束

    约束 定义
    struct T必须是值类型,比如int,结构等
    class T必须是引用类型,比如string、类
    base-class T是某个类型或者继承于某个类型,使用时用具体的类名替代,比如T:Animal
    interface T是某个接口或者继承了某个接口,如T:IMyInterface
    new() T必须有无参数的公共构造函数,如果new()用做约束,它必须是为类型T指定的最后一个约束

    如下例子示范了struct、class、base-class约束的使用。因为T1约束为值类型,所以定义泛型变量时T1只能是int等值类型,T2约束为引用类型,所以第二个参数类型必须是引用类型,T3使用base-class约束把T3约束为Animal或者继承于Animal的类。

    class MyGenericClass<T1, T2, T3> where T1:struct where T2:class where T3:Animal 
    {
    }
    
    
    MyGenericClass<int, string, Cow> generic = new MyGenericClass<int, string, Cow>();

    可用通过base-class约束把一个类型用做另一个类型的约束,这称为裸类型约束,表示两个参数类型是同一个类型,或者继承于另一个类型

     class MyGenericClass<T1, T2, T3> where T1:T2
     {
     }

    但是约束不能循环,如下定义是不能编译的

        class MyGenericClass<T1, T2, T3> where T1:T2 where T2:T1 
        {
        }

    约束可以是接口类型,表示T是该接口类型或者实现了这个这个接口,如果使用了new()约束,new()约束要放在每个where的最后

        class MyGenericClass<T1, T2, T3> where T1:MyInterface ,new() where T2:class
        {
        }

    创建泛型类实例

    abstract class Animal
        {
            protected string name;
            public string Name
            {
                get { return name; }
                set { name = value; }
            }
    
            public Animal()
            {
                name = "The animal has no name.";
            }
    
            public Animal(string newName)
            {
                name = newName;
            }
    
            public void Feed()
            {
                Console.WriteLine("{0}has been fed.", name);
            }
    
            public abstract void MakeANoise();
    
        }
    
    class Cow : Animal
        {
            public void Milk()
            {
                Console.WriteLine("{0}has been milked.", name);
            }
    
            public Cow(string newName)
                : base(newName)
            {
    
            }
    
            public override void MakeANoise()
            {
                Console.WriteLine("{0} say 'moo'",name);
            }
        }
    
        class Chicken : Animal {
            public void LagEgg()
            {
                Console.WriteLine("{0}has lag en egg.", name);
            }
    
            public Chicken(string newName)
                : base(newName)
            { 
                
            }
    
            public override void MakeANoise()
            {
                Console.WriteLine("{0} say 'Cluck'", name);
            }
        }
    
        class SuperCow : Cow
        {
            public SuperCow(string name)
                : base(name)
            { 
                
            }
    
            public void Fly()
            {
                Console.WriteLine("{0} is flying", name);
            }
    
            public override void MakeANoise()
            {
                Console.WriteLine("{0} say 'here i come to save the world'", name);
            }
        }
    
        class Farm<T>:IEnumerable<T> where T : Animal
        {
            private List<T> animals = new List<T>();
            public List<T> Animails
            {
                get { return animals; }
            }
    
            public void MakeNoises()
            {
                foreach (T animal in animals)
                { 
                    animal.MakeANoise();
                }
            }
    
            public void FeedTheAnimals()
            {
                foreach (T animal in animals)
                {
                    animal.Feed();
                }
            }
    
            public Farm<Cow> GetCows()
            {
                Farm<Cow> cows = new Farm<Cow>();
                foreach (T animal in animals)
                {
                    if (animal is Cow)
                        cows.Animails.Add(animal as Cow);
                }
    
                return cows;
            }
    
            //实现IEnumerable<T>接口用于迭代Farm<T>集合,实现该接口,需要实现下面两个方法
            public IEnumerator<T> GetEnumerator()
            {
                return animals.GetEnumerator();
            }
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                return animals.GetEnumerator();
            }
        }
    
        class Program
        {
    
            static void Main(string[] args)
            {
                Farm<Animal> farm = new Farm<Animal>();
                farm.Animails.Add(new Cow("Jack"));
                farm.Animails.Add(new Chicken("Vera"));
                farm.Animails.Add(new Chicken("Sally"));
                farm.Animails.Add(new SuperCow("Kavin"));
                farm.MakeNoises();
    
                Farm<Cow> cowFarm = farm.GetCows();
                foreach (Cow cow in cowFarm)
                {
                    if (cow is SuperCow)
                        (cow as SuperCow).Fly();
                    
                }
    
                Console.ReadLine();
            }
        }
    View Code

    该实例通过在内部利用List<T>泛型类实现自己的泛型类型,并实现IEnumerable<T>接口迭代集合。

     注:上面的泛型类继承了IEnumerable<T>,在Farm<T>提供的约束也会应用在IEnumerable<T>上。如果在基类上也定义了约束,则子类不能解除约束,也就是说类型T在子类必须受至少到与基类相同的约束。

    子类不能解除约束的意思是:如果子类没有约束,必须要在子类的定义中显式把父类的约束写出来。假如有一个子类SuperFarm<T>继承了Fram<T>类,它没有自己的约束,因为基类Farm<T>约束了T必须为Animal,所以子类定义时必须把这个约束显式写出

        //正确
        class SuperFarm<T> : Farm<T> where T:Animal 
        { 
        
        }
        //错误
        class SuperFarm<T> : Farm<T> 
        { 
        
        }

    如果子类有自己的约束,则子类的约束必须是父类的约束的一个子集,或者说子类约束中的对象必须能隐式转换为父类约束中的对象。如下约束T为Cow,Cow是Animal的一个子类,可以隐式转换为Animal。

        class SuperFarm<T> : Farm<T> where T:Cow 
        { 
        
        }

    如果一个类继承了泛型类,且改类不是泛型类,则它必须明确给出所有必须的类型信息,比如

        //正确
        class SuperFarm : Farm<Cow>
        { 
        
        }
    
        //错误的,没有给出类型T的具体类型信息
        class SuperFarm : Farm<T> 
        { 
        
        }

    1.3泛型运算符

    与其他类一样,泛型类可以对运算符进行重载。例如我们可以在Fram<T>中重载如下运算符,这样我们就能计算Farm<Animal> newFarm=farm+cowFarm这里的类型,其中cowFarm是Farm<Cow>的实例,farm是Farm<Animal>的实例,使用隐式转换运算符,我们就能把cowFarm隐式转换为List<Animal>类型,进而利用第二个运算符进行计算。

            public static implicit operator List<Animal>(Farm<T> farm)
            {
                List<Animal> result = new List<Animal>();
                foreach (T animal in farm)
                {
                    result.Add(animal);
                }
    
                return result;
            }
    
            public static Farm<T> operator +(Farm<T> farm1, List<T> farm2)
            {
                Farm<T> result=new Farm<T> ();
                
                foreach(T animal in farm1)
                {
                    result.Animails.Add(animal);
                }
    
                foreach(T animal in farm2)
                {
                    if(!result.Contains(animal))
                        result.animals.Add(animal);
                }
    
                return result;
            }

    第二个运算符中,第一个参数必须为Farm<T>类型,第二个参数必须为List<T>类型,为了使Farm<Animal> newFarm=cowFarm+farm能计算,再添加一个运算符,这个运算符利用了现有的运算符。

            public static Farm<T> operator +(List<T> farm1, Farm<T> farm2)
            {
                return farm2 + farm1;
            }

    有人说利用下面这个运算符也能实现Farm<Animal> newFarm=cowFarm+farm的运算

            public static Farm<T> operator +(Farm<T> farm1, Farm<T> farm2)
            {
                Farm<T> result = new Farm<T>();
    
                foreach (T animal in farm1)
                {
                    result.Animails.Add(animal);
                }
    
                foreach (T animal in farm2)
                {
                    if (!result.Contains(animal))
                        result.animals.Add(animal);
                }
    
                return result;
            }

    事实上这是不行的,因为Farm<Animal>和Farm<Cow>是不同的类型,他们不能进行计算。要使上面的式子能计算,必须重载隐式转换运算符,把Farm<Cow>转换为Farm<Animal>

            public static implicit operator Farm<Animal>(Farm<T> farm)
            {
                Farm<Animal> result = new Farm<Animal>();
                foreach (T animal in farm)
                {
                    result.animals.Add(animal);
                }
    
                return result;
            }

    从上面的例子可以看出,泛型中,如果类型T是继承的关系,一般都把子类转换为最基本的父类然后进行运算。

    2.泛型结构

    可以用与类类似的方式,定义泛型结构,只不过结构是值类型。

        public struct MyStruct<T1, T2>
        {
            public T1 item1;
            public T2 item2;
        }

     3.泛型接口

    泛型接口的定义和类一样,下面的泛型接口中T用作方法AttemptToBreed的参数类型和属性OldestInHerd的返回类型。

        interface MyInterface<T> where T : Animal
        {
            bool AttemptToBreed(T animal1, T animal2);
            T OldestInHerd { get; }
        }

    4.泛型方法

    之前的例子中有一个GetCow方法,假如我们需要再获取Chicken的集合,是不是需要再写一个方法,这样太麻烦了,泛型方法可以很好的解决这个问题。

            public Farm<Cow> GetCows()
            {
                Farm<Cow> cows = new Farm<Cow>();
                foreach (T animal in animals)
                {
                    if (animal is Cow)
                        cows.Animails.Add(animal as Cow);
                }
    
                return cows;
            }

    泛型方法的标准是方法声明中有个尖括号,中间是类型符T,比如我们可以添加一个泛型方法解决刚刚的问题。

            public Farm<U> GetSpecies<U>() where U:T
            {
                Farm<U> result = new Farm<U>();
                foreach (T animal in animals)
                {
                    if (animal is U)
                        result.animals.Add(animal as U);
                }
    
                return result;
            }

    注:泛型方法的类型参数不能用和Farm<T>相同的类型参数标识符,即Farm<T>用T做类型参数标识符,泛型方法就不能用T,因为它们表示的含义不一样。个人理解是Farm<T>是该类能处理的类型,GetSpecies<U>中的U是泛型方法能处理的类型,T和U可能没有半点联系。如可以在Farm<T>类中定义,如下方法,该泛型方法的U和类中的T没任何关系

            public T GetDefault<U>()
            {
                return default(T);
            }

    也可以在非泛型类中定义泛型方法,泛型方法可以和泛型类一样定义约束

        class MyDefault
        {
            public static T GetDefault<T>() where T:Animal
            {
                return default(T);
            }
        }

    5.泛型委托

    我们知道声明委托时,我们只要声明一个和方法签名、方法返回值一致的委托,就可以在程序中像声明对象一样使用这个委托,并把方法作为参数传递给委托变量,如下

        class Program
        {
            delegate int MyDelegate(int op1, int op2);
            static void Main(string[] args)
            {
                MyDelegate sum = new MyDelegate(Add);
                MyDelegate Multiply = new MyDelegate(Mul);
    
                Console.WriteLine(sum(1, 2));//3
                Console.WriteLine(Multiply(1, 2));//2
                Console.ReadLine();
            }
    
            public static int Add(int op1, int op2)
            {
                return op1 + op2;
            }
    
            public static int Mul(int op1, int op2)
            {
                return op1 * op2;
            }
        }

        声明泛型委托只需要使用一个或几个泛型参数。泛型委托和一般委托相比,处理的数据类型更大了。

        class Program
        {
            delegate T MyGenericDelegate<T>(T op1, T op2);
            static void Main(string[] args)
            {
                MyGenericDelegate<int> sum2 = new MyGenericDelegate<int>(Add);
                MyGenericDelegate<int> Multiply2 = new MyGenericDelegate<int>(Mul);
                Console.WriteLine(sum(1, 2));//3
                Console.WriteLine(Multiply(1, 2));//2
                Console.ReadLine();
            }
    
            public static int Add(int op1, int op2)
            {
                return op1 + op2;
            }
    
            public static int Mul(int op1, int op2)
            {
                return op1 * op2;
            }
        }

     6.变体

    协变和抗变(逆变)统称变体。

    我们知道使用 多态性可以把子类的对象放在基类的变量中,比如

    Cow myCow=new Cow("Cow");
    Animal myAnimal =myCow;

    那么对于泛型,像下面这样的代码能不能执行呢?

    List<Cow> cows=new List<Cow>();
    cows.Add(new Cow("Cow1"));
    List<Animal> animals = cows;

    答案是不能的,因为虽然Cow和Animal有继承关系, 但在泛型上类型List<Cow>和List<Animal>是两个不同的类型,它们不具有继承关系,不能互相转换。为了使上面的代码能运行,C#引入了另一个概念,协变。

    在泛型中,在类型参数T前面使用out关键字就可以定义协变,协变只能在接口和泛型委托中使用,用作方法或者get块的返回值。C#中使用协变的一个常用接口是IEnumerable<T>

        // 摘要:
        //     公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代。
        //
        // 类型参数:
        //   T:
        //     要枚举的对象的类型。
        [TypeDependency("System.SZArrayHelper")]
        public interface IEnumerable<out T> : IEnumerable
        {
            // 摘要:
            //     返回一个循环访问集合的枚举器。
            //
            // 返回结果:
            //     可用于循环访问集合的 System.Collections.Generic.IEnumerator<T>。
            IEnumerator<T> GetEnumerator();
        }

    因为IEnumerable<T>定义了协变,所以以下代码是正确的。List<T>实现了IEnumerable<T>,利用多态性可以把List<Cow>对象存在IEnumerable<Cow>变量中,利用协变可以把IEnumerable<Cow>对象放在IEnumerable<Animal>变量中。

                List<Cow> cows = new List<Cow>();
                cows.Add(new Cow("Cow1"));
                IEnumerable<Cow> iCows = cows;//多态性
                IEnumerable<Animal> iAnimals = iCows;//协变

    也可以在委托中定义协变

    delegate T MyGenericDelegate2<out T>();

    抗变:抗变和协变相反,抗变可以把泛型对象值存放在T类型的派生子类的泛型变量中。通过在类型变量T前面使用关键字in定义抗变,抗变只能用在接口和泛型委托中,用作方法参数。 比如IComparer<T>

        // 摘要:
        //     定义类型为比较两个对象而实现的方法。
        //
        // 类型参数:
        //   T:
        //     要比较的对象的类型。
        public interface IComparer<in T>
        {
            // 摘要:
            //     比较两个对象并返回一个值,指示一个对象是小于、等于还是大于另一个对象。
            //
            // 参数:
            //   x:
            //     要比较的第一个对象。
            //
            //   y:
            //     要比较的第二个对象。
            //
            // 返回结果:
            //     一个带符号整数,它指示 x 与 y 的相对值,如下表所示。值含义小于零x 小于 y。零x 等于 y。大于零x 大于 y。
            int Compare(T x, T y);
        }

    上面的接口定义了抗变,所以我们可以排序Cows把IComparer<Cow> 对象传给 IComparer<Animal>类型的变量,从而实现排序

        class AnimalComparer : IComparer<Animal>
        {
            public static AnimalComparer Default = new AnimalComparer();
    
           
            public int Compare(Animal x, Animal y)
            {
                return x.Name.Length.CompareTo(y.Name.Length) ;
            }
        }
    
    List<Cow> cows = new List<Cow>();
    cows.Add(new Cow("Cow22"));
    cows.Add(new Cow("Cow1"));
    cows.Add(new Cow("Cow111"));
    cows.Sort(AnimalComparer.Default);

    7.习题

    创建一个泛型类ShortCollection<T>,它实现了IList<T>接口,包含一个集合及集合的最大容量。这个最大容量可以通过构造函数设置,或者默认为10.构造函数还可以通过List<T>参数获取项的最初列表。该类与Collection<T>功能相同,但是如果试图添加超过最大容量的项,它会抛出IndexOutOfRangeException异常。

    class ShortCollection<T>:IList<T>
        {
            protected int maxSize = 10;
            protected Collection<T> innerCollection;
    
            public ShortCollection():this(10)
            { 
                
            }
    
            public ShortCollection(int size)
            {
                maxSize = size;
                innerCollection = new Collection<T>();
            }
    
            public ShortCollection(List<T> list):this(10,list)
            {
                
            }
    
            public ShortCollection( int size,List<T> list)
            {
                maxSize =size ;
                if (list.Count < maxSize)
                {
                    innerCollection = new Collection<T>(list);
                }
                else
                {
                    ThrowTooManyItemException();
                }
            }
    
    
            protected void ThrowTooManyItemException()
            {
                throw new IndexOutOfRangeException("Unable to add any more items,maxinum size is " + maxSize + " items.");
            }
            public int IndexOf(T item)
            {
                return innerCollection.IndexOf(item);
            }
    
            public void Insert(int index, T item)
            {
                if (innerCollection.Count < maxSize)
                    innerCollection.Insert(index, item);
                else
                    ThrowTooManyItemException();
            }
    
            public void RemoveAt(int index)
            {
                innerCollection.RemoveAt(index);
            }
    
            public T this[int index]
            {
                get
                {
                    return innerCollection[index];
                }
                set
                {
                    innerCollection[index]=value;
                }
            }
    
            public void Add(T item)
            {
                if (innerCollection.Count < maxSize)
                    innerCollection.Add(item);
                else
                    ThrowTooManyItemException();
            }
    
            public void Clear()
            {
                innerCollection.Clear();
            }
    
            public bool Contains(T item)
            {
                return innerCollection.Contains(item);
            }
    
            public void CopyTo(T[] array, int arrayIndex)
            {
                innerCollection.CopyTo(array, arrayIndex);
            }
    
            public int Count
            {
                get { return innerCollection.Count; }
            }
    
            public bool IsReadOnly
            {
                get { return (innerCollection as IList<T>).IsReadOnly; }
            }
    
            public bool Remove(T item)
            {
                return innerCollection.Remove(item);
            }
    
            public IEnumerator<T> GetEnumerator()
            {
                return innerCollection.GetEnumerator();
            }
    
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return innerCollection.GetEnumerator();
            }
        }
    View Code
  • 相关阅读:
    数据类型比较(==)
    uniapp(一)
    小程序分包
    小程序网易云(五)
    java.lang的详解
    有哪些日常节省时间的诀窍?
    怎么把知乎的回答转化成自己的知识?
    linux下搭建hadoop环境
    linux下,免密码登录
    mac下创建用户及赋予sudo权限
  • 原文地址:https://www.cnblogs.com/lidaying5/p/10623229.html
Copyright © 2020-2023  润新知