终于讲到泛型了。当初看到这个书名,最想看的就是作者对泛型,委托,反射这些概念的理解。很多人对泛型的理解停留在泛型集合上,刚开始我也是,随着项目越做越多,对待泛型的认识也越来越深刻。
泛型的概念:泛型是一种特殊的类型,它把指定类型的工作推迟到客户端代码声明并实例化类或方法的时候进行。
泛型的优势:源代码保护、类型安全、更加清晰的代码、更佳的性能。
原理:(关键字:开放类型,封闭类型)所有带泛型参数的类型都是一个开放式类型,它不能被实例化(类似接口),在具体使用时生成封闭类型(实际数据类型)。
泛型约束(至多一个主要约束,次要约束无限制):
泛型约束的使用:
/// <summary> /// 未加约束的T可以是任何类型,许多类型没有提供CompareTo方法,没有约束将导致代码不能编译,报"'T'不包含'CompareTo'的定义"错误 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="o1"></param> /// <param name="o2"></param> /// <returns></returns> private static T Min<T>(T o1,T o2) where T : IComparable<T> { if (o1.CompareTo(o2) < 0) return o1; return o2; }
构造器约束:
//因为所有值类型都隐式有一个公共无参构造器。约束要求指定的任何引用类型也要有一个公共无参构造器 internal sealed class ConstructorConstraint<T> where T:new(){ public static T Factory(){ return new T(); } }
由于泛型类型参数不能指定以下特殊引用类型:System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum,System.Void,一些实参限制的实现可能要“特殊处理”,如以下使用静态构造器来保证类型是一个枚举类型。
internal sealed class GenericTypeThatRequiresAnEnum<T>{ static GenericTypeThatRequiresAnEnum(){ if(!typeof(T).IsEnum){ throw new ArgumentException("T must be an enumerated type"); } } }
泛型接口的优势:没有泛型接口,每次视图使用一个非泛型接口来操作一个值类型,都会发生装箱,而且会失去编译时的类型安全性。
委托和接口的逆变和协变泛型类型实参:
不变量:意味着泛型类型参数不能更改。(常用)
逆变量:意味着泛型类型参数可以从一个基类更改为该类的派生类。在C#中,用in关键字标记逆变量形式的泛型类型参数。逆变量泛型类型参数只出现在输入位置,比如作为方法的参数。
协变量:意味着泛型类型参数可以从一个派生类更改为它的基类。在C#中,是用out关键字标记协变量形式的泛型类型参数。协变量泛型类型参数只能出现在输出位置,比如作为方法的返回类型。
public delegate TResult Func<in T,out TResult>(T arg);
其它重要认知:
类型实参的指定和继承层次结构没有任何关系--理解这一点,有助于你判断转型的进行。
C#允许使用简化的语法来引用一个泛型封闭类型
using DateTimeList = System.Collections.Generic.List<System.DateTime>;
现在执行下面这行代码时,sameType会被初始化为true:
Boolean sameType = (typeof(List<DateTime>) == typeof(DateTimeList));
CLR支持泛型委托,目的是保证任何类型的对象都能以一种类型安全的方式传给一个回调方法。此外,泛型委托允许任何一个值类型实例在传给一个回调方法时不执行任何装箱处理。
一些验证问题:
1. 泛型类型变量的转型
将一个泛型类型的变量转型为另一个类型是非法的,除非将其转型为另一个约束兼容的类型
private static void CastingType<T>(T obj){ Int32 x = (Int32)obj;//错误 String s = (String)obj;//错误 string s2 = obj as String;//无错误 }
2. 设定默认值
private static void SettingDefaultValue<T>(){ T temp = default(T); }
default关键字告诉C#编译器和CLR的JIT编译器,如果T是一个引用类型,就将temp设为null,如果T是一个值类型,就将temp的所有位设为0。