• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

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

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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

 


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#有哪几种定时器发布时间:2022-07-13
下一篇:
C#中读取流媒体视频文件转H.264具体实现方法发布时间:2022-07-13
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap