在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
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、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(); } } 该实例通过在内部利用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(); } }
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论