为了促进代码重用,尤其是算法的重用,C#支持一个名为泛型的特性。
泛型与模块类相似。
泛型使算法和模式只需要实现一交。而不必为每个类型都实现一次。在实例化的时候,传入相应的数据类型便可。
注:可空值类型 也是基于泛型实现的。
泛型的本质 就是给某些复杂的类型,抽象一套数据类型的别名。然后可以在这复杂类型当中使用这些别名。
当运行时,可以通过为这一套抽象的数据类型指定实际的数据类型。
1、概述
利用泛型,可以在声明变量时创建用来处理特定类型的特殊数据结构。程序员定义这种参数化类型,
使特定泛型类型的每个变量都有相同的内部算法,但数据类型和方法签名可以根据程序员的偏爱而不同。
语法也与C++模块相似。
所以在C#中,泛型类和结构的语法要求使用相同的尖括号表示法来表示泛型声明要处理的数据类型。
2、简单泛型类的定义
在类声明之后,需要在一对尖括号中指定一个类型参数标识符或者类型参数。
1 public class Stack<T> 2 { 3 private T[] _Items; 4 5 public void Push(T data) 6 { 7 8 } 9 10 public void Pop() 11 { 12 13 } 14 }
泛型的优点:
1、泛型提供了一个强类型的编程模型。它确保在参数化的类中,只有成员明确希望的数据类型才可以使用。
2、编译时类型检查减少了在运行时发生InvalidCastException异常的几率。
3、为泛型类成员使用值类型,不再造成object的类型转换,它们不再需要装箱操作。
4、C#中的泛型缓解了代码膨胀的情况。
5、性能得到了提高。一个原因是不再需要从object的强制转换,从而避免了类型检查。
另一个是不再需要为值类型执行装箱。
6、泛型减少了内存消耗。由于避免了装箱,因此减少了堆上的内存的消耗。
7、代码的可读性更好。
8、支持IntelliSense的代码编辑器现在能直接处理来自泛型类的返回参数。没有必要为了使IntelliSense工作起来,而对返回
数据执行转型。
2、编译时类型检查减少了在运行时发生InvalidCastException异常的几率。
3、为泛型类成员使用值类型,不再造成object的类型转换,它们不再需要装箱操作。
4、C#中的泛型缓解了代码膨胀的情况。
5、性能得到了提高。一个原因是不再需要从object的强制转换,从而避免了类型检查。
另一个是不再需要为值类型执行装箱。
6、泛型减少了内存消耗。由于避免了装箱,因此减少了堆上的内存的消耗。
7、代码的可读性更好。
8、支持IntelliSense的代码编辑器现在能直接处理来自泛型类的返回参数。没有必要为了使IntelliSense工作起来,而对返回
数据执行转型。
4、类型参数命名的指导原则
和方法参数的命名相似,类型参数的命名应该尽量具有描述性。
除此之外,为了强调它是一个类型参数,名称就包含一个T前缀。
5、泛型接口与struct
C#2.0支持在C#语言中全面地使用泛型,其中包括接口和struct。
语法和类使用的语法完全相同。
要定义包含类型参数的一个接口,将类型参数放到一对尖括号中即可。
1 interface IPair<T> 2 { 3 T First { get; set; } 4 T Second { get; set; } 5 } 6 public struct Pair<T> : IPair<T> 7 { 8 private T _First; 9 public T First 10 { 11 get 12 { 13 return _First; 14 } 15 set 16 { 17 _First = value; 18 } 19 } 20 private T _Second; 21 public T Second 22 { 23 get 24 { 25 return _Second; 26 } 27 set 28 { 29 _Second = value; 30 } 31 } 32 33 34 }
注:实现接口时,语法与非泛型类的语法是相同的。然而,如果实现一个泛型接口,同时不指定类型参数,会强迫类成为一个泛型类。
不过此例使用了struct而不是类,表明C#支持自定义的泛型值类型。
对于泛型接口的支持对于集合类来说尤其重要,使用泛型最多的地方就是集合类。假如没有泛型
,开发者就要依赖于System.Collections命名空间中的一系列接口。
6、在一个类中多次实现相同的接口
模块接口造成的另一个结果是,可以使用不同的类型参数来多次实现同一个接口。
1 public interface IContainer<T> 2 { 3 ICollection<T> Items 4 { set; get; } 5 } 6 public class Address 7 { 8 } 9 public class Phone 10 { 11 } 12 public class Email 13 { 14 } 15 16 public class Person : IContainer<Address>, IContainer<Phone>, IContainer<Email> 17 { 18 ICollection<Address> IContainer<Address>.Items 19 { set; get; } 20 21 ICollection<Phone> IContainer<Phone>.Items 22 { set; get; } 23 24 ICollection<Email> IContainer<Email>.Items 25 { set; get; } 26 }
7、构造器和终结器的定义
泛型的构造器和析构器不要求添加类型参数来与类的声明匹配。
1 2 interface IPair<T> 3 { 4 T First { get; set; } 5 T Second { get; set; } 6 } 7 public struct Pair<T> : IPair<T> 8 { 9 public Pair(T first, T second) 10 { 11 _First = first; 12 _Second = second; 13 } 14 private T _First; 15 public T First 16 { 17 get 18 { 19 return _First; 20 } 21 set 22 { 23 _First = value; 24 } 25 } 26 private T _Second; 27 public T Second 28 { 29 get 30 { 31 return _Second; 32 } 33 set 34 { 35 _Second = value; 36 } 37 } 38 39 }
在构造器当中,必须对所有成员变量进行初始化。
因为一个成员变量在泛型中,有可能是值类型的,也有可能是引用类型的。所以需要显式赋值。否则统一初始化null是不合适的。
不过可以使用default运算符对任意数据类型的默认值进行动态编码。
1 public Pair(T first) 2 { 3 _First = first; 4 _Second = default(T); 5 }
注:default运算符允许在泛型的上下文之外使用,任何语句都可以使用它。
8、多个类型参数
泛型类型可以使用任意数量的类型参数,在前面的Pair<T>,只包含一个类型参数,为了存储不同类型的两个对象,比如一个"名称/值"对
,需要支持两个或者更多的类型参数。
1 interface IPair<TFirst,TSecond> 2 { 3 TFirst First { get; set; } 4 TSecond Second { get; set; } 5 } 6 public struct Pair<TPFirst, TPSecond> : IPair<TPFirst, TPSecond> 7 { 8 public Pair(TPFirst first,TPSecond second) 9 { 10 _First = first; 11 _Second = second; 12 } 13 private TPFirst _First; 14 public TPFirst First 15 { 16 get 17 { 18 return _First; 19 } 20 set 21 { 22 _First = value; 23 } 24 } 25 private TPSecond _Second; 26 public TPSecond Second 27 { 28 get 29 { 30 return _Second; 31 } 32 set 33 { 34 _Second = value; 35 } 36 } 37 38 }
同样,只需要在声明和实例化语句的尖括号中指定多个类型参数,然后提供调用方法时与该方法的参数相匹配的类型。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 Pair<int, string> historicalEvent = new Pair<int, string>(1914, "Shackletion leaves for South Pole on ship Endurance"); 7 Console.WriteLine("{0}:{1}",historicalEvent.First,historicalEvent.Second); 8 9 10 } 11 }
9、元数
在C#4.0中,CLR团队定义了9个新的泛型类型,它们都叫Touple。和Pair<...>一样,相同的名称可以重用,因为它们的元数不同(
每个类都有不同数量的类型参数)。
可以通过元数的不同重载类型定义。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static class Tuple { // 摘要: // 创建新的 1 元组,即单一实例。 // // 参数: // item1: // 元组仅有的分量的值。 // // 类型参数: // T1: // 元组的唯一一个分量的类型。 // // 返回结果: // 值为 (item1) 的元组。 public static Tuple<T1> Create<T1>(T1 item1); // // 摘要: // 创建新的 2 元组,即二元组。 // // 参数: // item1: // 此元组的第一个分量的值。 // // item2: // 此元组的第二个分量的值。 // // 类型参数: // T1: // 此元组的第一个分量的类型。 // // T2: // 元组的第二个分量的类型。 // // 返回结果: // 值为 (item1, item2) 的 2 元组。 public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2); // // 摘要: // 创建新的 3 元组,即三元组。 // // 参数: // item1: // 此元组的第一个分量的值。 // // item2: // 此元组的第二个分量的值。 // // item3: // 此元组的第三个分量的值。 // // 类型参数: // T1: // 此元组的第一个分量的类型。 // // T2: // 元组的第二个分量的类型。 // // T3: // 元组的第三个分量的类型。 // // 返回结果: // 值为 (item1, item2, item3) 的 3 元组。 public static Tuple<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3); // // 摘要: // 创建新的 4 元组,即四元组。 // // 参数: // item1: // 此元组的第一个分量的值。 // // item2: // 此元组的第二个分量的值。 // // item3: // 此元组的第三个分量的值。 // // item4: // 此元组的第四个分量的值。 // // 类型参数: // T1: // 此元组的第一个分量的类型。 // // T2: // 元组的第二个分量的类型。 // // T3: // 元组的第三个分量的类型。 // // T4: // 此元组的第四个分量的类型。 // // 返回结果: // 值为 (item1, item2, item3, item4) 的 4 元组。 public static Tuple<T1, T2, T3, T4> Create<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, T4 item4); // // 摘要: // 创建新的 5 元组,即五元组。 // // 参数: // item1: // 此元组的第一个分量的值。 // // item2: // 此元组的第二个分量的值。 // // item3: // 此元组的第三个分量的值。 // // item4: // 此元组的第四个分量的值。 // // item5: // 此元组的第五个分量的值。 // // 类型参数: // T1: // 此元组的第一个分量的类型。 // // T2: // 元组的第二个分量的类型。 // // T3: // 元组的第三个分量的类型。 // // T4: // 此元组的第四个分量的类型。 // // T5: // 此元组的第五个分量的类型。 // // 返回结果: // 值为 (item1, item2, item3, item4, item5) 的 5 元组。 public static Tuple<T1, T2, T3, T4, T5> Create<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5); // // 摘要: // 创建新的 6 元组,即六元组。 // // 参数: // item1: // 此元组的第一个分量的值。 // // item2: // 此元组的第二个分量的值。 // // item3: // 此元组的第三个分量的值。 // // item4: // 此元组的第四个分量的值。 // // item5: // 此元组的第五个分量的值。 // // item6: // 此元组的第六个分量的值。 // // 类型参数: // T1: // 此元组的第一个分量的类型。 // // T2: // 元组的第二个分量的类型。 // // T3: // 元组的第三个分量的类型。 // // T4: // 此元组的第四个分量的类型。 // // T5: // 此元组的第五个分量的类型。 // // T6: // 此元组的第六个分量的类型。 // // 返回结果: // 值为 (item1, item2, item3, item4, item5, item6) 的 6 元组。 public static Tuple<T1, T2, T3, T4, T5, T6> Create<T1, T2, T3, T4, T5, T6>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6); // // 摘要: // 创建新的 7 元组,即七元组。 // // 参数: // item1: // 此元组的第一个分量的值。 // // item2: // 此元组的第二个分量的值。 // // item3: // 此元组的第三个分量的值。 // // item4: // 此元组的第四个分量的值。 // // item5: // 此元组的第五个分量的值。 // // item6: // 此元组的第六个分量的值。 // // item7: // 元组的第七个分量的值。 // // 类型参数: // T1: // 此元组的第一个分量的类型。 // // T2: // 元组的第二个分量的类型。 // // T3: // 元组的第三个分量的类型。 // // T4: // 此元组的第四个分量的类型。 // // T5: // 此元组的第五个分量的类型。 // // T6: // 此元组的第六个分量的类型。 // // T7: // 元组的第七个分量的类型。 // // 返回结果: // 值为 (item1, item2, item3, item4, item5, item6, item7) 的 7 元组。 public static Tuple<T1, T2, T3, T4, T5, T6, T7> Create<T1, T2, T3, T4, T5, T6, T7>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7); // // 摘要: // 创建新的 8 元组,即八元组。 // // 参数: // item1: // 此元组的第一个分量的值。 // // item2: // 此元组的第二个分量的值。 // // item3: // 此元组的第三个分量的值。 // // item4: // 此元组的第四个分量的值。 // // item5: // 此元组的第五个分量的值。 // // item6: // 此元组的第六个分量的值。 // // item7: // 元组的第七个分量的值。 // // item8: // 元组的第八个分量的值。 // // 类型参数: // T1: // 此元组的第一个分量的类型。 // // T2: // 元组的第二个分量的类型。 // // T3: // 元组的第三个分量的类型。 // // T4: // 此元组的第四个分量的类型。 // // T5: // 此元组的第五个分量的类型。 // // T6: // 此元组的第六个分量的类型。 // // T7: // 元组的第七个分量的类型。 // // T8: // 元组的第八个分量的类型。 // // 返回结果: // 值为 (item1, item2, item3, item4, item5, item6, item7, item8) 的 8 元祖(八元组)。 public static Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>> Create<T1, T2, T3, T4, T5, T6, T7, T8>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8); }
这一组的Tuple<...>类是出于和Pari<T>与Pair<TPFirst,TPSecond>类相同的目的而设计的。
只是在不特殊处理的前提下,它们最多能同时处理7个类型参数。
当其中一个类型参数实际值为Tuple时,元组的大小实际可以实现无限的。
可以支持普通的创建方法,也支持静态方法Create来创建。(此为一工厂方法)。
10、嵌套泛型类型
嵌套类型自动获得包容类型的类型参数。相当于在包容类型当中,这个类型参数是一个新的数据类型,可以和其它数据类型一样使用。
如果嵌套类型包含了自己的类型参数,会隐藏包容类型当中的同名类型参数。
1 class Container<T, U> 2 { 3 class Nested<U> 4 { 5 void Method(T param0, U param1) 6 { 7 8 } 9 } 10 } 11
注:在合法的情况下,可以将类型参数转换为指定的接口,或者类。为了保证合法性,就需要一些约束性。
11、约束
泛型允许为类型参数定义约束。这些约束强迫类型遵守各种规则。
C#允许为泛型类中声明的每个类型参数提供一个可行的约束列表。约束声明了泛型要求的类型参数的特征。
为了声明一个约束,需要使用where关键字,后跟一对"参数:要求"。其中,"参数"必须是泛型类型中定义的一个参数,而
"要求"用于限制类型从中派生的类或接口,或者限制必须存在一个默认构造器,或者限制使用一个 引用/值 类型约束。
11、1 接口约束
1 public class BinaryTree<T> where T : System.IComparable<T> 2 { 3 4 }
指定,实际使用的类型参数T,必须实现了IComparable<T>接口。如此T的变量也可以直接调用IComparable接口的方法。而不用显式转换。
有了这种约束之后,甚至不需要执行转型,就可以调用一个显式的接口成员实现。
11、2 基类约束
1 public class BinaryTree<T> where T : Phone 2 { 3 4 }
基类约束的语法与接口约束基本相同。
但是,如果同时指定了多个约束,那么基类约束必须第一个出现。
而且不允许在一个约束中将参数参数限制为必须从String或者System.Nullable<T>派生。
11、3 struct/class约束
另一个重要的泛型约束是将类型参数限制为一个值类型或者引用类型。
1 public class BinaryTree<T> where T : struct 2 { 3 4 } 5 public class BinaryTree<T> where T : class 6 { 7 8 }
struct约束有一个非常特别的地方。一方面,它规定类型参数只能为值类型;另一方面,
它禁止将System.Nullable<T>作为类型参数。
原因如下:因为假如没有这个限制,就可以定义毫无意义的Nullable<Nullable<T>>类型。之所以毫无意义,
是因为Nullable<T>本身允许可空的值类型变量,而一个可空的"可空类型"是毫无意义的。
由于可空运算符(?)是C#用于声明一个可空值类型的快捷方式,因此sturct约束对Nullable<T>的限制同时
会禁止你写下面这样的代码:
int ?? number; //等价于Nullable<Nullable<T>>
11、4 多个约束
对于任何给定的类型参数,都可以指定任意数量的接口作为约束,但基类约束只能指定一个,
因为一个类可以实现任意数量的接口,但肯定只能从一个类继承。
每个新约束都在一个以逗号分隔的列表中声明,约束列表跟在泛型类型名称和一个冒号之后。
如果有多个类型参数,那么每个类型名称的前面都要使用一个 where 关键字
1 public class BinaryTree<T, U> : Container<T, U> 2 where T : IComparable<T>, IFormattable 3 where U : Phone 4 { 5 6 }
注:在where 之间并不存在逗号
11、5 构造器约束
某些情况下,需要在泛型类的内部创建类型参数的一个实例。
所以需要用约束来规定必须有一个默认构造器
1 public class BinaryTree<T, U> : Container<T, U> 2 where T : IComparable<T>, IFormattable 3 where U : Phone,new () 4 { 5 6 }
new()来指定构造器约束。
11、6 约束继承
约束可以由一个派生类继承,但必须与基类一样,需要在派生类中显式地指定这些约束。
否则会引发编译错误。
1 public class EntityBase<T> where T:IComparable<T> 2 { 3 4 } 5 public class Entity<T> : EntityBase<T> where T : IComparable<T> 6 { 7 8 }
重写一个虚泛型方法是,或者创建一个显式接口方法实现时,约束是隐式继承的,不需要重新声明。
1 public class EntityBase<T> where T:IComparable<T> 2 { 3 public virtual void Method<T>(T t) 4 where T : IComparable<T> 5 { 6 7 } 8 } 9 public class Entity<T> : EntityBase<T> where T : IComparable<T> 10 { 11 public override void Method<T>(T t) 12 { 13 14 } 15 }
在继承的情况下,不仅可以保留基类本来的约束(这是必须的),还可添加额外的约束,
从而对基类的类型参数进行进一步的限制。
但是,重写的成员需要遵守基类方法中定义的“接口”。
额外的约束可能破坏多态性,所以不允许新增约束,而且重写方法上的参数约束是隐式继承的。
12 、重点: 约束的限制
待查
1、不允许运算符约束
不能用约束来规定一个类必须支持一个特定的方法或者运算符,除非那个方法 或运算符在一个接口上。
2、不支持OR条件
默认为AND的。如果支持OR,编译器就无法在编译时判断要调用哪一个方法。
3、委托和枚举类型的约束是无效的
将任何委托类型作为一个类约束来使用是一种不被允许的约束。
所有委托类型都被视为是不能指定为类型参数的特殊类。
4、只允许默认构造器约束
不能要求支持其他非默认的构造器。
13、泛型方法
泛型方法是即使包容类不是泛型类,或者方法包含的类型参数不在泛型类的类型参数列表中,
也依然使用泛型的方法。
为了定义泛型方法,需要紧接在方法名之后添加类型参数语法。
1 public static class MathEx 2 { 3 public static T Max<T>(T first, params T[] values) 4 where T : IComparable<T> 5 { 6 T maximun = first; 7 foreach (T item in values) 8 { 9 if (item.CompareTo(maximun) > 0) 10 { 11 maximun = item; 12 } 13 } 14 return maximun; 15 } 16 }
注:方法可以是静态,或者是成员方法。
泛型方法和在相似,可以包含多个类型参数。类型参数的数量是一中可用来区分方法签名的特性。同理类。
13、1 类型推断
只要类型是隐式兼容的,类型推断是根据参数来判断实际使用的参数类型。
从而省略提类型参数。
1 Console.WriteLine(MathEx.Max<int>(7, 490)); 2 Console.WriteLine(MathEx.Max<string>("R.O.U.S.","Fireswamp")); 3 4 Console.WriteLine(MathEx.Max(7, 490)); 5 Console.WriteLine(MathEx.Max("R.O.U.S.","Fireswamp"));
如果类型推断出错,可以显式转型或者指定类型实参。
13、2 约束的指定
泛型方法也允许指定约束。
约束紧接在方法头的后面,但位于构成方法主体的大括号之前。
1 public virtual void Method<T>(T t) 2 where T : IComparable<T> 3 { 4 5 }
13、3 泛型方法中的转型
有时应该避免使用泛型-----例如,在使用它会造成一次转型操作被“掩盖”起来的时候。
在方法中执行显式转型,相较于在泛型版本中执行隐式转型,前者能够更加明确地描述所
发生的事情。开发者在泛型方法中执行转型时,假如没有约束来验证转型的有效性,需要小心。
14、协变性和逆变性
如果用不同的类型参数声明同一个泛型类的两个变量,变量不是类型兼容的,即使是将一个较具体的类型
赋给一个较泛化的类型。也就是说:它们不是协变量。
例如:Contact Address是从PdaItem派生的
虽然两个类型参数是兼容的,但是Pair<Contact>与Pair<PdaItem>
之间不允许转换。
Pair<Contact> contacts=new Pair<Contact>((new Contact());
Pair<PdaItem> paditem=new Pair<PdaItem>((new PdaItem());
Pair<Contact>和Pair<PdaItem>这两个数据类型之间不可以随便转换。以及其它类似牵扯到的转换(比如泛型接口、委托等)。
如:IPair<PdaItem> 与 Pair<Contact> contacts 之间的转换
协变:从子类往基类转换(较具体的往较泛化的转换)
逆变:从基类向子类转换(较泛化的往较具体的转换)
注:因为数据项可能是异质的。禁止协变性可维持同质性 待查。
14、1 在C#4.0中使用out类型参数修饰符允许协变性
可能的协变性
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //只有接口和委托可以使用in out修饰 6 Pair<Contact> contacts = new Pair<Contact>(new Contact("Princess Buttercupt", DateTime.Now), 7 new Contact("Inigo Montoya", DateTime.Now)); 8 //IPair<PdaItem> pair = contacts;//因为没有使用out ,不允许协变 9 IReadOnlyPair<PdaItem> readPair = contacts; 10 //异质:泛型指定的参数数据类型与实际的数据类型不同且非继承关系() 11 //异质的产生: 12 //因为如果允许从子类往上转型成了基类,理论上可以改变 pair.ReadOnlyFirst = new Address(); 13 //因为Address是PdaItem的子类 会造成数据的异质 14 //本来只允许包含的是Contact类型(T被指定为Contact),现在却包含了Address 这个没有直接关系的类型 15 //所以才需要使用通过out修饰限制泛型类型声明,让它只向接口的外部公开数据 16 17 PdaItem pdaItem1 = readPair.ReadOnlyFirst;//只可读取,不可设置 18 PdaItem pdaItem2 = readPair.ReadOnlySecond; 19 Console.WriteLine(pdaItem1.Name + " " + pdaItem1.LastUpdated); 20 Console.WriteLine(pdaItem2.Name + " " + pdaItem2.LastUpdated); 21 22 23 24 25 26 27 28 } 29 } 30 public class PdaItem 31 { 32 public PdaItem() 33 { 34 } 35 public PdaItem(string pName, DateTime pLastUpdated) 36 { 37 Name = pName; 38 LastUpdated = pLastUpdated; 39 } 40 public virtual string Name { set; get; } 41 42 public DateTime LastUpdated { set; get; } 43 } 44 45 public class Contact : PdaItem 46 { 47 public override string Name 48 { 49 get 50 { 51 return FirtstName; 52 } 53 set 54 { 55 FirtstName = value + " from Contact"; 56 } 57 } 58 private string FirtstName; 59 public Contact() 60 { 61 } 62 public Contact(string pName, DateTime pLastUpdated) 63 : base(pName, pLastUpdated) 64 { 65 66 } 67 68 } 69 70 public class Address : PdaItem 71 { 72 public override string Name 73 { 74 get 75 { 76 return DetailAddress; 77 } 78 set 79 { 80 DetailAddress = value + " from Address"; 81 } 82 } 83 //此处会造成与Contact数据不一,从而造成泛型的数据异质 84 private string DetailAddress; 85 public string address1; 86 public string address2; 87 public string address3; 88 public Address() 89 { 90 } 91 public Address(string pName, DateTime pLastUpdated) 92 : base(pName, pLastUpdated) 93 { 94 95 } 96 97 } 98 99 interface IReadOnlyPair<out T> 100 { 101 T ReadOnlyFirst { get; } 102 T ReadOnlySecond { get; } 103 } 104 interface IPair<T> 105 { 106 T First { get; set; } 107 T Second { get; set; } 108 } 109 public struct Pair<T> : IPair<T>, IReadOnlyPair<T> 110 { 111 112 public Pair(T first, T second) 113 { 114 TPFirst = TPFirstReadOnly = first; 115 TPSecond = TPSecondReadOnly = second; 116 } 117 private T TPFirst; 118 private T TPSecond; 119 private T TPFirstReadOnly; 120 private T TPSecondReadOnly; 121 122 T IPair<T>.First 123 { 124 get 125 { 126 return TPFirst; 127 } 128 set 129 { 130 TPFirst = value; 131 } 132 } 133 T IPair<T>.Second 134 { 135 get 136 { 137 return TPSecond; 138 139 } 140 set 141 { 142 TPSecond = value; 143 } 144 } 145 T IReadOnlyPair<T>.ReadOnlyFirst 146 { 147 get 148 { 149 return TPFirstReadOnly; 150 } 151 } 152 T IReadOnlyPair<T>.ReadOnlySecond 153 { 154 get 155 { 156 return TPSecondReadOnly; 157 } 158 159 } 160 }
注:如果没有out,就会报错,通过限制泛型类型声明,让它只向接口的外部公开数据,编译器就没有理由禁止协变性了。
在C#4.0中,对有效协变性的支持(被赋值的类型只对外公开数据)是通过out类型参数修饰符来添加的。
用out来修饰的类型参数,会导致编译器验证T真的只用于成员的返回和属性的取值方法(get访问方法),永远不用于输入参数或者属性
的赋值方法(set访问器方法)。在此之后,编译器就会允许对接口的任何协变赋值操作了。
14.2 在C#4.0中使用in类型参数修饰符允许逆变性
Pair<PdaItem> pair=new Pair<PdaItem>(new Contact(),new Address());
Pair<Contact> contact = (Pair<Contact>)pair;
逆变性,从 Pair<PdaItem>往Pair<Contact>转换,但因为项有Contact也有Address 所以完全强制转换成Contact会无效,
可以通过对泛型接口和委托使用in来实现
假定有一个接口,通过这个接口,只有Contact才
能放到First和Second中,在这种情况下,Pair<PdaItem>
原本存储的是什么东西就不重要了(因为这个接口不能
获取其中的东西,而通过Pair<PdaItem>直接赋值一个
Address也不会影响到这个接口的有效性
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //只有接口和委托可以使用in out修饰 6 Pair<Contact> contacts = new Pair<Contact>(new Contact("Princess Buttercupt", DateTime.Now), 7 new Contact("Inigo Montoya", DateTime.Now)); 8 //IPair<PdaItem> pair = contacts;//因为没有使用out ,不允许协变 9 IReadOnlyPair<PdaItem> readPair = contacts; 10 //异质:泛型指定的参数数据类型与实际的数据类型不同且非继承关系() 11 //异质的产生: 12 //因为如果允许从子类往上转型成了基类,理论上可以改变 pair.ReadOnlyFirst = new Address(); 13 //因为Address是PdaItem的子类 会造成数据的异质 14 //本来只允许包含的是Contact类型(T被指定为Contact),现在却包含了Address 这个没有直接关系的类型 15 //所以才需要使用通过out修饰限制泛型类型声明,让它只向接口的外部公开数据 16 17 PdaItem pdaItem1 = readPair.ReadOnlyFirst;//只可读取,不可设置 18 PdaItem pdaItem2 = readPair.ReadOnlySecond; 19 Console.WriteLine(pdaItem1.Name + " " + pdaItem1.LastUpdated); 20 Console.WriteLine(pdaItem2.Name + " " + pdaItem2.LastUpdated); 21 22 23 //从基类转换成子类的泛型 24 Pair<PdaItem> pdaitem = new Pair<PdaItem>(new Contact("Princess Buttercupt", DateTime.Now), 25 new Address()); 26 IWriteOnlyPair<Contact> writePair = pdaitem; 27 //此处是重点,通过这个接口 1、只能进行赋值且只能是Contact类型或者它的子类,而不能是Address类型(非相关类型) 2、不能进行访问 28 //这样就避免了,当pdaitem有多个不同类型的项时,使用一个泛型(T为子类类型)接口访问数据时,产生错误。因为类型不同,内部的成员变量和方法也有可能不同。 29 30 writePair.WriteOnlyFirst = new Contact(); 31 writePair.WriteOnlySecond = new Contact(); 32 33 34 } 35 } 36 public class PdaItem 37 { 38 public PdaItem() 39 { 40 } 41 public PdaItem(string pName, DateTime pLastUpdated) 42 { 43 Name = pName; 44 LastUpdated = pLastUpdated; 45 } 46 public virtual string Name { set; get; } 47 48 public DateTime LastUpdated { set; get; } 49 } 50 51 public class Contact : PdaItem 52 { 53 public override string Name 54 { 55 get 56 { 57 return FirtstName; 58 } 59 set 60 { 61 FirtstName = value + " from Contact"; 62 } 63 } 64 private string FirtstName; 65 public Contact() 66 { 67 } 68 public Contact(string pName, DateTime pLastUpdated) 69 : base(pName, pLastUpdated) 70 { 71 72 } 73 74 } 75 76 public class Address : PdaItem 77 { 78 public override string Name 79 { 80 get 81 { 82 return DetailAddress; 83 } 84 set 85 { 86 DetailAddress = value + " from Address"; 87 } 88 } 89 //此处会造成与Contact数据不一,从而造成泛型的数据异质 90 private string DetailAddress; 91 public string address1; 92 public string address2; 93 public string address3; 94 public Address() 95 { 96 } 97 public Address(string pName, DateTime pLastUpdated) 98 : base(pName, pLastUpdated) 99 { 100 101 } 102 103 } 104 105 interface IReadOnlyPair<out T> 106 { 107 T ReadOnlyFirst { get; } 108 T ReadOnlySecond { get; } 109 } 110 interface IWriteOnlyPair<in T> 111 { 112 T WriteOnlyFirst { set; } 113 T WriteOnlySecond { set; } 114 } 115 interface IPair<T> 116 { 117 T First { get; set; } 118 T Second { get; set; } 119 } 120 public struct Pair<T> : IPair<T>, IReadOnlyPair<T>, IWriteOnlyPair<T> 121 { 122 123 public Pair(T first, T second) 124 { 125 TPFirst = TPFirstWriteOnly = TPFirstReadOnly = first; 126 TPSecond = TPSecondWriteOnly = TPSecondReadOnly = second; 127 } 128 private T TPFirst; 129 private T TPSecond; 130 private T TPFirstReadOnly; 131 private T TPSecondReadOnly; 132 133 private T TPFirstWriteOnly; 134 private T TPSecondWriteOnly; 135 136 //以下都是显式实现接口,使用时,需要进行显式转换才可以使用 137 T IPair<T>.First 138 { 139 get 140 { 141 return TPFirst; 142 } 143 set 144 { 145 TPFirst = value; 146 } 147 } 148 T IPair<T>.Second 149 { 150 get 151 { 152 return TPSecond; 153 154 } 155 set 156 { 157 TPSecond = value; 158 } 159 } 160 T IReadOnlyPair<T>.ReadOnlyFirst 161 { 162 get 163 { 164 return TPFirstReadOnly; 165 } 166 } 167 T IReadOnlyPair<T>.ReadOnlySecond 168 { 169 get 170 { 171 return TPSecondReadOnly; 172 } 173 174 } 175 T IWriteOnlyPair<T>.WriteOnlyFirst 176 { 177 set 178 { 179 TPFirstWriteOnly = value; 180 } 181 } 182 T IWriteOnlyPair<T>.WriteOnlySecond 183 { 184 set 185 { 186 TPSecondWriteOnly = value; 187 } 188 189 } 190 }
14、3 协变性和逆变性类型修饰符可以用在同一个接口上
1 interface IConvertible<in TSource, out TTarget> 2 { 3 TTarget Convert(TSource s); 4 }
通过以上接口,就可以成功地从一个
IConvertible<PdaItem,Contact>转换成一个IConvertible<Contact,PdaItem>
15 泛型的内部机制
泛型类的"类型参数"变成了元数据,“运行时”
在需要的时候会利用它们构造恰当的类。所以泛型
支持继承、多态性以及封装。
使用泛型时,可以定义方法、属性、字段、类、接口以及委托。
15、1 基于值类型的泛型的实例化
使用一个值类型作为类型参数来首次构造一个泛型类型时,
“运行时”会将指定的类型参数放到CIL中合适的位置,从而创建一个
具体化的泛型类型。所以,“运行时”会为每个新的参数值类型创建新的
具体化泛型类型。
比如:
第一次使用Stack<int>类型时,“运行时”会生成Stack类的一个具体版本,
并用int替换它的类型参数。从这以后,每当代码使用Stack<int>的时候,
“运行时”都会重用已生成的具体化Stack<int>类。
使用具体化的值类型的类,好处在于能获得较好的性能。
除此之外,代码能避免转换和装箱,因为每个具体的泛型都“天生地”包含值类型。
15、2 基于引用类型的泛型实例化
使用一个引用类型作为类型参数来首次构造一个泛型类型时,
“运行时”会创建一个具体化的泛型类型,并在CIL代码中用object引用
,而不是基于类型参数的一个具体泛型类型)替换类型参数。
以后会相同模板的泛型会使用这同一个类。用类型的泛型类创建的具体化类被减少到了一个
,所以泛型极大地减少了代码量。