------- Windows Phone 7手机开发、.Net培训、期待与您交流! -------
泛型
我们在编写程序时,经常会遇到两个模块功能非常相似,但方法的参数类型不同的情况。即使可以把方法的代码复制一份,改变参数来实现功能。但是如果还需要第三种第四种参数不同的情况,还是复制代码显然就不太好了。泛型的出现解决了这个问题。泛型使用一个通用的数据类型T来代替object,在类实例化时知道T的类型,运行时自动编译为本地代码,运行效率和代码质量都有很大提高。
什么是泛型
泛型(Generic)是具有占位符(类型参数)的类、结构、接口和方法。C#中的泛型能够将类型作为参数来传递,即在创建类型时,事先使用一个特定的符号(如T等)代替实际的类型。在创建该类型的实例时,才指定T的实际类型。泛型可以让类、结构、接口、委托和方法按它们存储和操作的数据类型进行参数化。泛型能提供更强的编译时类型检查,减少数据类型之间的显式转换,以及装箱操作和运行时的类型检查。使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
类型形参
类型形参是一个简单标识符,它只起占位符的作用,直到在使用时才为其指定实际类型。类型形参放置在类名后,并在“<”和“>”分隔符中指定。每一个类型形参所代表的实际类型由其所在实例创建时指定。特别地,该实例将存储类型形参的数据而不进行数据类型转换。
下面声明一个名称为Test<T>的泛型类。其中,T为类型形参。
public class Test<T> //声明一个泛型类Test
{
}
下面创建Test<T>泛型类的实例t,并指定该实例的类型形参的类型为int。
Test<int> t = new Test<int>(); //创建泛型类的实例t
注意:在类声明中,每个类型形参在该类的声明空间中定义一个名称,而且不能与另一个类型形参或该类中声明的成员具有相同的名称。当然,类型形参也不能与类型本身具有相同的名称。
下面声明名称为Test<T,U>的泛型类。其中,T和U都为类型形参。
public class Test<T,U> //声明有两个类型形参的泛型类Test
{
}
设置类型形参的约束
使用类型形参可以给我们带来很多好处,同时,也可以对类型形参进行约束。通常情况下类型形参的类型可以指定为任何类型。然而,一些类型形参需要指定一定种类的类型,如结构、类等。为了实现该功能,就需要限制类型形参的类型,这种限制被称为约束。即在定义泛型类时,可以限制在实例化类时用于类型参数的类型种类。如果尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。
类型形参的约束使用where上下文关键字指定,后跟一个类型形参和一个冒号,再跟着一个逗号分隔的列表。列表项可以是类类型、接口类型甚或类型形参(还可以是特殊引用类型、值类型和构造函数约束)。
下面列出了常用的6种类型的约束。
T:结构,类型参数必须是值类型,但不能是Nullable类型。
T:类,类型参数必须是引用类型。
T:new(),类型参数必须具有无参数的公共构造函数。如果该类型参数存在多个约束,则new()约束必须最后指定。
T:<基类名>,类型参数必须是指定的基类或者其派生类。
T:<接口名称>,类型参数必须是指定的接口或实现指定的接口。
T:U,T的类型参数必须是U提供的参数或派生自为U提供的参数。
下面创建名称为Test<T>泛型类型,并指定约束。该约束指定T的类型参数必须为值类型(除了Nullable类型之外)。
public class Test<T> where T : struct //为泛型类Test添加值类型约束
{
}
下面创建名称为“Test<T>”泛型类型,并指定了约束。该约束指定T的类型参数必须为引用类型(class指定)或者具有无参数的公共构造函数的类型(new()指定)。
public class Test<T> where T : class,new() //为泛型类添加引用类型或者new构造函数约束
{
}
通常情况下,对于一个给定的类型形参而言,其约束的接口和类型形参的数目不受限制,但只能有一个类。
下面创建一个名称为Test<T>泛型类型,并指定了约束。该约束指定T的类型参数必须为引用类型,且需要实现Iinterface和Iinterface<T>接口。
public class Test<T> where T :class,Iinterface,Iinterface<T> //为泛型类添加约束,T的类型参数必须为引用类型,且要实现指定的两个接口
{
}
注意:类型形参约束虽然提供更强的编译时类型检查,并在某些情况下改进了性能,但是它们也使泛型类型的使用受到限制。
声明泛型类
泛型类和普通类一样,使用之前也必须声明。因此,泛型类声明也遵循类声明的规则。一个泛型类一般至少包含一个类型形参。当创建该泛型类的实例时,需要指定该类型形参的具体类型。
下面声明一个名称为Test<T>的泛型类。
public class Test<T>
{
}
下面创建Test<T>泛型类的实例a,并指定该实例的类型形参的类型为string。
Test<string> a = new Test<string>();
泛型类的成员
泛型类是一种特殊的类,也有自己的成员。泛型类的所有成员都可以直接使用其所属类的类型形参。在运行时,类型形参都被替换为相应的实际类型(该类型在创建类的实例时指定)。
下面声明一个名称为Test<T>的泛型类。其中,T为类型形参。另外,还要在该类中声明一个私有字段t(类型为T)和一个方法GetValue()(返回值的类型为T)。
public class Test<T>
{
private T t; //定义私有变量t,类型为T
public T GetValue() //获取t变量的值
{
return t;
}
}
下面如果使用“Test<int> te = new Test<int>();”创建了Test<T>泛型类的实例te,那么,实际上上述代码已经被解释为如下代码。
public class Test<int>
{
private int t; //定义私有变量t,类型为int
public int GetValue() //获取t变量的值
{
return t;
}
}
泛型类的静态字段
泛型类中也可以定义静态字段。泛型类中的静态字段的共享方法比较特殊,即只有在具有相同类型形参的类型的实例才能够共享同一个静态字段的值。
下面声明一个名称为Test<T>的泛型类,并在该类中声明一个静态字段count和一个静态属性Count。count静态字段的初始化值为0,并在Test<T>泛型类的构造函数中增1。Count静态属性获取count静态字段的值。
public class Test<T>
{
static int count = 0; //静态字段count,初始化值为0
public Test()
{
count++; //在构造函数中,将count静态字段的值增1
}
}
public static int Count //静态属性Count,获取count静态字段的值
{
get
{
return count;
}
}
下面创建Test<T>泛型类的3个实例:t1、t2和t3。其中,t1和t2实例的类型形参的类型为int,而t3实例的类型形参的类型为float。因为t1和t2实例的类型形参的类型相同,因此,它们能够共享count静态字段的值。此时,countt1的值为1,countt2的值为2。而t3实例和t1或者t2实例的类型形参的类型都不相同,因此,t3实例和t1或者t2实例都不能共享count静态字段的值。此时,countt3的值为1。
Test<int> t1 = new Test<int>(); //创建Test<T>泛型类的实例t1,其中T为int类型
int countt1 = t1.Count; //countt1的值为1
Test<int> t2 = new Test<int>(); //创建Test<T>泛型类的实例t2,其中T为int类型
int countt2 = t2.Count; //countt2的值为2
Test<float> t3 = new Test<float>(); //创建Test<T>泛型类的实例t3,其中T为float类型
int countt3 = t3.Count; //countt3的值为1
泛型方法
泛型方法和泛型类性质相同,都使用类型形参,也可以对类型形参加以约束。泛型方法可以声明在类、结构或接口中,而这些类、结构或接口本身可以是泛型或非泛型的。如果这些类、结构或接口也是泛型类型,那么在这些方法内既可以引用该泛型方法的类型形参,也可以使用这些类、结构或接口的类型形参。
声明泛型方法
泛型方法和普通方法一样,在使用之前必须声明。和普通方法相比,除了普通的参数之外,泛型方法还必须包含类型形参,即泛型方法是指在声明中包含类型形参的方法。
下面在Test类中声明一个名称为“Swap<T>(ref T left,ref T right)”的泛型方法。该方法交换left和right参数的数据,参数的类型由T类型形参决定。其中,T为类型形参,left和right为引用参数。
01 public class Test
02 {
03 public void Swap<T>(ref T left,ref T right)
04 //声明泛型方法Swap,该方法交换left和right参数的数据
05 {
06 T temp; //创建临时变量
07 temp = left;
08 left = right;
09 right = temp;
10 }
11 }
下面在Test<T>泛型类中声明一个名称为“Swap<U>(ref U left,ref U right)”的泛型方法。该方法交换left和right参数的数据,参数的类型有U类型形参决定。其中,U为类型形参,left和right为引用参数。
01 public class Test<T>
02 {
03 public void Swap<U>(ref U left,ref U right)
04 //声明泛型方法Swap,交换left和right参数的数据
05 {
06 U temp; //创建临时变量
07 temp = left;
08 left = right;
09 right = temp;
10 }
11 }
注意:Test<T>泛型类和Swap<U>(ref U left,ref U right)泛型方法都包含了类型形参。如果把Swap<U>(ref U left,ref U right)泛型方法的声明修改为“Swap<T>(ref T left,ref T right)”,则在编译该代码时,系统显示一个警告。那是因为Swap<T>(ref T left,ref T right)泛型方法中的T类型形参会覆盖Test<T>泛型类的T类型形参。
调用泛型方法
方法写好了之后,就可以进行调用。调用泛型方法和调用普通方法一样,但需要为类型形参提供相应的类型实参列表。泛型方法调用可以显式指定类型实参列表,或者可以省略类型实参列表并依赖类型推断确定类型实参。
下面创建Test类的实例t,并创建类型为int的两个变量:i和j(它们的值分别为10和20),然后调用Test类的Swap<T>(ref T left,ref T right)泛型方法。
Test t = new Test(); //创建Test类的实例t
int i = 10; //创建类型为int的i变量
int j = 20; //创建类型为int的j变量
t.Swap<int>(ref i,ref j); //调用了Swap<T>(ref T left,ref T right)泛型方法
另外,在调用泛型方法时,如果编译器可以推断出类型形参的值,那么可以省略类型形参。
下面创建Test类的实例t,并创建类型为int的两个变量:i和j(它们的值分别为10和20),然后调用了Test类的Swap<T>(ref T left,ref T right)泛型方法,并省略了类型形参。
Test t = new Test(); //创建Test类的实例t
int i = 10; //创建类型为int的i变量
int j = 20; //创建类型为int的j变量
t.Swap<int>(ref i,ref j); //调用了Swap<T>(ref T left,ref T right)泛型方法
通过委托调用泛型方法
调用泛型方法除了像调用普通方法一样的方式之外,还可以通过委托来调用泛型方法。
下面程序中声明PrintDelegate委托,该委托的参数列表为“int i,int j”,返回类型为int。然后在Test类中声明一个泛型方法F<T>(T i)。最后在Main()函数中创建Test类的示例t,创建PrintDelege委托的实例pd,并用pd调用t的F<T>(T i)泛型方法。
namespace ConsoleApplication
{
class Program
{
public delegate int PrintDelegate(int i);
public class Test
{
public int F<T>(int i)
{
return i;
}
}
static void Main(string[] args)
{
Test t = new Test();
PrintDelegate pd = new PrintDelegate(t.F<int >);
Console.WriteLine("通过委托PrintDelegate调用T型方法F<T>(T i)\n"+ pd(10));
Console.ReadLine();
}
}
}
注意:在通过委托调用泛型方法时,泛型方法也必须符合委托的签名。
虚泛型方法
和普通方法一样,泛型方法也存在虚泛型方法,它也可以使用abstract、virtual和override修饰符进行声明。
下面声明Program和Test类,且Test类派生自Program类。Program类中声明一个名称为“Swap<T>(ref T left,ref T right)”的虚泛型方法,Test类使用“Swap<U>(ref U left,ref U right)”重写该虚泛型方法。这两个方法都实现left和right参数的数据,参数的类型由T或U类型形参决定。其中,T和U都为类型形参,left和right为引用参数。
namespace ConsoleApplication
{
class Program
{
public virtual void Swap<T>(ref T left, ref T right)
//声明虚泛型方法 Swap ,交换 left 和 right 数据
{
T temp; //创建零时变量
temp = left;
left = right;
right = temp;
}
public class Test : Program
{
public override void Swap<T>(ref T left, ref T right) //重写虚方法 Swap
{
base.Swap<T>(ref left, ref right);
}
static void Main(string[] args)
{
int left = 10, right = 20;
Test t = new Test();
t.Swap<int>(ref left, ref right);
Console.WriteLine("left = " + left + "\n" + "right = " + right);
Console.ReadLine();
}
}
}
}