NET CLR via C#读书笔记 - 第十二章 泛型
1 泛型简介
泛型是另一种形式的“代码重用”,或者说“算法重用”。开发者可以定义好算法,排序,搜索,交换等,但是这些算法的数据类型却不会事先指定,而是由使用者去决定,一个排序算法可以操作int和string等类型对象。
CLR允许创建泛型引用类型和泛型值类型,不允许创建泛型枚举类型。CLR允许在引用类型,值类型或者接口中定义泛型方法。
使用泛型的优点有一下几点:
① 源代码保护,使用泛型的开发人员不需要知道泛型的具体实现,只需要知道相关定义即可,C++模板的泛型技术必须公开源代码。
② 类型安全,试图使用不兼容类型的对象会造成编译时错误,或者运行时异常。
③ 清晰的代码逻辑,由于编译器的强制类型安全性,不需要在代码使用强制装换指明转换类型
④ 性能优势,泛型算法相比于常规化算法,用来值类型的实例时会进行装箱操作,在数据量较大时,会比较严重的损耗性能,使用泛型算法时,所有值类型以传值的方式进行传递,不会造成装箱操作。
2 开放类型和封闭类型
开放类型:具有泛型类型参数的类型,CLR禁止创建开放类型的实例。当所有类型参数都传递了实际类型参数时就成了封闭类型,可以创建封闭类型的实例。
为泛型类型的泛型类型参数指定具体类型时,CLR会在内部创建创建该具体类型对象的内部数据结构,且每个封闭类型都有自己独立的静态字段,举个例子,加入List中定义了任意一个静态字段,但是在List定义的静态字段不会在List和List中共享,他们是相互独立的,静态构造器也是同样的规则,针对List和List会分别执行它们自己的静态构造器。
3 泛型类型和继承
泛型类型也是类型,所以也可以从其它类型派生。
例如List从object派生,那么List和List也从object派生。
假如直接定义以下这个链表节点类型:
internal sealed class NODE<T>
{
public T m_data;
public NODE<T> m_next;
public NODE(T data) : this(data, null) { }
public NODE(T data, NODE<T> next)
{
this.m_data = data;
this.m_next = next;
}
public override string ToString()
{
return m_data.ToString() + ((m_next != null) ? m_next.ToString():string.Empty);
}
}
调用代码如下:
NODE<char> head = new NODE<char>('C');
head = new NODE<char>('B', head);
head = new NODE<char>('A', head);
Console.WriteLine(head.ToString());
输出结果:
在以上这个示例中,所有的数据节点的类型在使用时是确定的,不能同时存储int和char这两种类型。
那如果需要同时存储多种数据类型的链表可以如何实现呢?
方法一:可以使用非泛型方法NODE,不过这种方法会导致编译时的类型安全丢失,而且值类型会频繁装箱。
方法二:定义非泛型NODE基类,在定义泛型GNODE类。具体实现如下:
internal class NODE
{
protected NODE m_next;
public NODE(NODE next)
{
m_next = next;
}
}
internal sealed class GNODE<T> : NODE
{
public T m_data;
public GNODE(T data) : this(data, null) { }
public GNODE(T data,NODE next) : base(next)
{
m_data = data;
}
public override string ToString()
{
return m_data.ToString() + ((m_next != null) ? m_next.ToString() : string.Empty);
}
}
调用如下:
NODE ghead = new GNODE<char>('.');
ghead = new GNODE<int>(20210926, ghead);
ghead = new GNODE<string>("Today is ", ghead);
Console.WriteLine(ghead.ToString());
输出结果(ABC为第一种方式输出结果):
4 泛型类型同一性
使用using简化写法而不要使用新增类定义来简化代码,新增类会导致同一性丢失,具体看如下代码:
新增类定义:
internal sealed class IntList : List<int>{}
IntList iT = new IntList();
bool sameType = (typeof(List<int>) == typeof(IntList ))
运行结果 sameType 为 false
使用using语法:
using IntList = System.Collections.Generic.List<int>;
bool sameType = (typeof(List<int>) == typeof(IntList ))
运行结果 sameType 为 true
5 可验证性和约束
5.1 主要约束
泛型类型参数可以指定0或者多个主要约束,主要约束是代表非密封类的一个引用类型,不能指定为System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum,System…Void.指定主要约束后,相当于向编译器承诺,后续指定的类型实参要么是约束类型,要么是约束类型的派生类型,语法如下:
internal sealed class Test<T> where T : Stream{
public void M(T stream){
stream.Close();
}
}
class 和 struct 是两个特殊的主要约束,class表明实参必须是引用类型,struct表明泛型实参指定时要为引用类型。
5.2 次要约束
次要约束可以有0个或者多个,次要类型有接口类型约束和类型参数约束两种,接口类型约束在第十三章中展开,类型参数约束是规定了泛型类型的指定实参,要么就是约束类型,要么就是约束类型的派生类型。代码如下:
private static List<TBase> ConvertList<T,TBase>(IList<T> list) where T : TBase{
List<TBase> baseList = new List<TBase>(list.Count);
for(int index = 0;index < list.Count; index++)
{
baseList.Add(List[index]);
}
return baseList;
}
ConvertList指定了两个类型参数,其中T为TBase的类型参数约束,T指定的任何类型实参都必须兼容于TBase指定的类型实参。
5.3 构造器约束
构造器约束可以有0个或者多个,泛型类型指定的类型实参实现了公共无参构造器的非抽象类型。
class TestCtor<T> where T : new(){
public static T Factory() {
return new T();
}
}
5.4 泛型类型变量转型
将泛型类型的变量转型为其它类型(与约束类型不兼容的)是非法的。
5.5 泛型类型变量设为默认值
使用default 关键字
class TestCtor<T>(){
T temp = default(T);
}
5.5 泛型类型变量与null比较
无论泛型是否被约束,使用==或者!= 操作符与null作比较都是合法的。
5.6 两个泛型变量作比较
如果两个泛型变量是引用类型,是合法的,如果是都为值类型,如果没有重载==或者!=操作符,则是非法的。
5.7 不要将泛型类型变量作为操作数使用
编译器在编译时确定不了类型,不能向泛型类型的变量应用任何操作符。
以上内容为对《NET CLR via C#(第四版)》第十二章内容的阅读笔记,只记录其中核心部分内容,书中对以上主题还进行详细的代码演示说明,书中还讨论了泛型接口和泛型委托,如有需要,请详细阅读本书第十二章内容,感谢您的阅读。