为了促进代码重用,尤其是算法的重用,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<...>一样,相同的名称可以重用,因为它们的元数不同(
每个类都有不同数量的类型参数)。
可以通过元数的不同重载类型定义。
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); }