C#学习笔记---C#2.0范型
3.范型的实例化
跟不是范型的类型类似,编译后的范型也表现为IL指令和元数据.当然范型也会编码已存在和使用的类型参数.
在构造范型类型,比如stack<int>的实例在程序中被构造时,.NET的CLR JIT编译器将会把IL指令和元数据转化成本地代码,在这个过程中将会用实际类型替换类型参数.以后对该构造范型类型的引用将会使用同一份本地代码.从范型类型创建一个特定的构造范型类型的过程叫做范型类型的实例化(Generic Type Instantiation).
.NET运行时会为每个用值类型进行的范型的实例化创建一份特定的本地代码拷贝,但是用引用类型进行的范型的实例化会共享一份本地代码(因为在本地代码层次,引用是具有同样表现形式的指针).
4.约束
如果想用type parameters 调用某些方法会出现什么情况那?
为了支持更强的编译时检查和减少类型转化带来的开销,C#允许为每个类型参数提供一个可选的约束(Constraints)列表.Constraints的定义以where+类型参数+类或者接口类型的类表[+构造器约束new()].
对于范型Dictionary<K,V>为了确保其中的keys实现IComparable接口,可以对K做一个约束.
5.范型方法
在某些情况下,并不需要整个类是范型,而只需要某个类的方法成员是范型的.这经常发生在当把一个范型类型做为一个方法的参数时.比如Stack<T>这个范型,当想把一组中的多个值一次性压入栈的时候,写一个方法把这些值在一个方法调用时实现这个功能是比较方便的.对于一个给定的构造类型,比如Stack<int>,这个方法可以用如下方式定义:
跟不是范型的类型类似,编译后的范型也表现为IL指令和元数据.当然范型也会编码已存在和使用的类型参数.
在构造范型类型,比如stack<int>的实例在程序中被构造时,.NET的CLR JIT编译器将会把IL指令和元数据转化成本地代码,在这个过程中将会用实际类型替换类型参数.以后对该构造范型类型的引用将会使用同一份本地代码.从范型类型创建一个特定的构造范型类型的过程叫做范型类型的实例化(Generic Type Instantiation).
.NET运行时会为每个用值类型进行的范型的实例化创建一份特定的本地代码拷贝,但是用引用类型进行的范型的实例化会共享一份本地代码(因为在本地代码层次,引用是具有同样表现形式的指针).
4.约束
如果想用type parameters 调用某些方法会出现什么情况那?
public class Dictionary<K,V>
{
public void Add(K key, V value)
{
if (key.CompareTo(x) < 0) {} // Error, no CompareTo method
}
}
因为key被定义为可以为任意类型,所以key只能调用object声明的方法成员,比如Equals, GetHashCode和ToString,当然可以把key转化为有CompareTo定义的类型,比如转化为IComparable类型.{
public void Add(K key, V value)
{
if (key.CompareTo(x) < 0) {} // Error, no CompareTo method
}
}
public class Dictionary<K,V>
{
public void Add(K key, V value)
{
if (((IComparable)key).CompareTo(x) < 0) {}
}
}
尽管这个解决方案可用,但是会引起运行时检查,并把错误延迟到运行时,抛出InvalidCastException 异常.{
public void Add(K key, V value)
{
if (((IComparable)key).CompareTo(x) < 0) {}
}
}
为了支持更强的编译时检查和减少类型转化带来的开销,C#允许为每个类型参数提供一个可选的约束(Constraints)列表.Constraints的定义以where+类型参数+类或者接口类型的类表[+构造器约束new()].
对于范型Dictionary<K,V>为了确保其中的keys实现IComparable接口,可以对K做一个约束.
public class Dictionary<K,V> where K: IComparable
{
public void Add(K key, V value)
{
if (key.CompareTo(x) < 0) {}
}
}
对于一个给定的类型参数,可以指定多个Interface做为约束,但是只能指定至多一个类做为约束,这个跟继承的规则是一样的,每个有约束条件的类型参数有一个单独的where子句.构造器约束new()规定被用做一个类型参数的类型argument必须有一个public类型的,无参的构造函数,这样就允许用new ()的方式构造指定类型的实例.{
public void Add(K key, V value)
{
if (key.CompareTo(x) < 0) {}
}
}
5.范型方法
在某些情况下,并不需要整个类是范型,而只需要某个类的方法成员是范型的.这经常发生在当把一个范型类型做为一个方法的参数时.比如Stack<T>这个范型,当想把一组中的多个值一次性压入栈的时候,写一个方法把这些值在一个方法调用时实现这个功能是比较方便的.对于一个给定的构造类型,比如Stack<int>,这个方法可以用如下方式定义:
void PushMultiple(Stack<int> stack, params int[] values) {
foreach (int value in values) stack.Push(value);
}
这个方法可以将多个int值压入栈.但是这个方法用于特定的构造类型,为了让它跟Stack<T>协同工作,必须定义范型方法.范型方法可以在方法名后面带多个类型参数,这些类型参数可以在参数列表,返回类型和方法体中,一个范型PushMultiple 可以像如下定义:foreach (int value in values) stack.Push(value);
}
void PushMultiple<T>(Stack<T> stack, params T[] values) {
foreach (T value in values) stack.Push(value);
}
调用方式如下:foreach (T value in values) stack.Push(value);
}
Stack<int> stack = new Stack<int>();
PushMultiple<int>(stack, 1, 2, 3, 4);
这样这个方法可以复用,但是有个不方便的地方就是每次调用都要指定<int>,这有些不太方便,但是在很多情况下,编译器能够根据其他参数的类型推断出该方法正确的参数类型,这个过程成为类型推断(type inferencing),比如前一个例子,由于第一个参数是Stack<int>类型的,后面的参数也是int的,所以可以推断出参数类型一定是int,所以这个方法也可以不指定类型参数,调用方式如下:PushMultiple<int>(stack, 1, 2, 3, 4);
Stack<int> stack = new Stack<int>();
PushMultiple(stack, 1, 2, 3, 4);
PushMultiple(stack, 1, 2, 3, 4);