目录
正文
一、C#泛型及机制
1、通过代码初识泛型
class Stack<T>
{
private T[] store;
Private int size;
……
}
上面代码,蓝色部分表示泛型类型,T表示类型参数,T在泛型类中可以当做一个类型来使用(如用作方法的参数、返回值,字段的类型等)。
2、泛型的定义
所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型。泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用。
某个系统需要实现一个方法。该方法需要对int、byte两种类型的参数进行处理,这就需要重载方法,即写两个方法。但这两个方法除了参数类型不同之外,基本上是一致的。代码存在重复。
使用泛型可以将类型参数设置为int或byte,因此,只需要写一个泛型方法即可实现上面所说的功能。(通过使用泛型可以更好的复用代码。)
3、泛型的优势
C#泛型赋予了代码更强的类型安全,更好的复用,更高的效率(性能)。
例说泛型的优势
现有定义:ArrayList arrayList = new ArrayList(); 可以向arrayList中添加任何对象。如果向其中添加int类型的变量,那么在Add时需要将int类型的变量隐式装箱成引用类型。添加后要从arrayList取出数据,需要进行强制类型转换,将引用类型转换成int类型,这就有一个拆箱的过程。
当使用泛型时,需要指定可以向arrayList中添加数据的类型(比如int),在从arrayList取出数据时,类型也是固定的(int)。这就省去了装箱和拆箱的步骤。因此,泛型提供了更高的效率(性能)。
还是上面的实例,如果在向arrayList添加对象时,添加了一个string类型,那么在取出数据时,强制类型转换就会出错,而这种错误在编译时是无法发现的。使用泛型则要求类型必须一致(int),这就省去了强制类型转换的步骤,不会因为强制类型转换而产生错误。因此,泛型提供了更强的类型安全。
泛型还提供了更好的复用,参见泛型定义。
4、泛型的编译机制
第一轮编译时,编译器只为Stack<T>类型产生“泛型版”的IL代码与元数据----并不进行泛型类型的实例化,T在中间只充当占位符。
JIT编译时,当JIT编译器第一次遇到Stack<int>时,将用int替换“泛型版”IL代码与元数据中的T----进行泛型类型的实例化。
CLR为所有类型参数为“引用类型”的泛型类型产生同一份代码;但如果类型参数为“值类型”,对每一个不同的“值类型”,CLR将为其产生一份独立的代码。
关于编译过程请参考编译浅见。
C#的泛型能力由CLR在运行时支持。
5、C#泛型的几个特点
1、如果实例化泛型类型的参数相同,那么JIT编译器会重复使用该类型。
2、C#泛型类型携带有丰富的元数据,因此C#的泛型类型可以应用于强大的反射技术。
3、C#的泛型采用“基类、接口、构造器、值类型/引用类型”的约束方式来实现对类型参数的“显示约束”。 详见泛型约束。
二、泛型类型
1、C#泛型类与结构
C#除可单独声明泛型类型(包括类与结构)外,也可在基类中包含泛型类型的声明。但基类如果是泛型类,它的类型参数要么已实例化,要么来源于子类(同样是泛型类型)声明的类型参数。
class C<U, V> { }
//合法(单独声明的泛型类,没有继承)
class D : C<string, int> { }
//合法(基类是泛型类,它的类型参数已经实例化)
class E<U, V> : C<U, V> { }
//合法(基类是泛型类,它的类型参数来源于子类<同样是泛型类型>声明的类型参数)
class F<U, V> : C<string, int> { }
//合法(基类是泛型类,它的类型参数已经实例化)
class G : C<U, V> { }
//非法(基类是泛型类,它的类型参数应该来源于子类声明的类型参数,但子类不是泛型类)
2、泛型类型的成员
泛型类型的成员可以使用泛型类型声明中的类型参数。但类型参数如果没有约束,则只能在该类上使用从System.Object继承的共有成员。详见泛型约束。
class C<V>
{
public V f1;//声明字段
public D<V> f2; //作为其他泛型类型的参数
public C(V x)
{
this.f1 = x;
}
}
3、泛型接口
泛型接口的类型参数要么已实例化,要么来源于实现类声明的类型参数。
interface IList<T>
{
T[] GetElements();
}
interface IDictionary<K, V>
{
void Add(K key, V value);
}
class List<T> : IList<T>, IDictionary<int, T>
{
public T[] GetElements()
{
return null;
}
public void Add(int key, T value)
{
}
}
4、泛型委托
泛型委托支持在委托返回值和参数上应用类型参数,这些类型参数同样可以附带合法的约束。详见泛型约束。
delegate bool Predicate<T>(T value);
class X
{
static bool F(int i) { return false; }
static bool G(string s) { return true; }
static void Main()
{
Predicate<int> p1 = new Predicate<int>(F);
Predicate<string> p2 = G;
}
}
三、泛型方法
1、泛型方法简介
C#泛型机制只支持“在方法声明上包含类型参数”----即泛型方法。
C#泛型机制不支持在除方法外的其他成员(包括属性、事件、索引器、构造器、析构器)的声明上包含类型参数,但这些成员本身可以包含在泛型类型中,并使用泛型类型的类型参数。
泛型方法既可以包含在泛型类型中,也可以包含在非泛型类型中。
2、泛型方法的声明与调用
public class Finder
{
//泛型方法的声明
public static int Find<T>(T[] itmes, T item) { return -1; }
}
//泛型方法的调用
int i = Finder.Find<int>(new int[] {1,3,4,5,6,8,9 }, 6);
3、泛型方法的重载
class MyClass
{
void F1<T>(T[] a, int i); //不可以构成重载方法
void F1<U>(U[] a, int i);
void F2<T>(int x); //可以构成重载方法
void F2(int x);
void F3<T>(T t) where T : A; //不可以构成重载方法
void F3<T>(T t) where T : B;
}
重载方法的含义详见重载方法。
4、泛型方法的重写
abstract class Base
{
public abstract T F<T, U>(T t, U u) where U : T;
public abstract T G<T>(T t) where T : IComparable;
class Derived : Base
{
public override T F<T, U>(T t, U u) { }
//合法的重写,约束被默认继承
public override T G<T>(T t) where T:IComparable { }
//非法的重写,指定任何约束都是多余的
}
}
四、泛型约束
1、泛型约束简介
C#泛型要求对“所有泛型类型或泛型方法的类型参数”的任何假定,都要基于“显示的约束”,以维护C#所要求的类型安全。
“显示约束”由where子句表达,可以指定“基类约束”,“接口约束”,“构造器约束”,“值类型/引用类型约束”共四种约束。
“显示约束”并非必须,如果没有指定“显示约束”,泛型类型参数将只能访问System.Object类型中的公有方法。
2、基类约束
class A { public void F1() { } }
class B { public void F2() { } }
class C<S, T>
where S : A //S继承自A
where T : B //T继承自B
{
//可以在类型为S的变量上调用F1,
//可以在类型为T的变量上调用F2
}
3、接口约束
interface IPrintable { void Print();}
interface IComparable<T> { int CompareTo(T v);}
interface IKeyProvider<T> { T GetKey();}
class Dictionary<K, V>
where K : IComparable<K>
where V : IPrintable, IKeyProvider<K>
{
//可以在类型为K的变量上调用CompareTo,
//可以在类型为V的变量上调用Print和GetKey
}
4、构造器约束
class A { public A() { } }
class B { public B(int b) { } }
class C<T> where T : new()
{
//可以在其中使用T t = new T();
}
C<A> c = new C<A>();//可以,A有无参构造器
C<B> c = new C<B>();//错误,B没有无参构造器
目前C#泛型只支持无参构造器的约束。
5、值类型/引用类型约束
public struct A { }
public class B { }
class C<T>
where T : struct
{
//T在这里是一个值类型
}
C<A> c = new C<A>(); //可以,A是一个值类型
C<B> c = new C<B>(); //错误,B是一个引用类型
五、补充
1、重载方法
方法的签名一致。即方法名称一致,参数列表(类型或个数)不一致。
2、编译浅见
第一步,将源代码编译成IL和元数据。
第二步,CLR在运行时的JIT编译。将IL代码编译成机器代码。
JIT采用ON-DEMAND的方式进行实例化。用到才实例化,不用就不实例化。
3、类型参数T能不能使用属性?
C#泛型目前不支持参数类型T使用属性。可以使用如下办法来实现。创建一个类C,使C包含T要使用的所有属性。再通过基类约束,显示约束T继承C。即可使用C中定义的属性。
4、C#所支持的泛型类型
C#的泛型支持包括类、结构、接口、委托共四种泛型类型,以及方法成员。