泛型
如果一个类成员允许使用多种数据类型(例如设计一个通用的数据结构),一种实现方法是将类成员指定为object类型,但这种写法代码可读性较差。C#2.0引入了泛型,泛型可以实现将类成员的类型作为一个参数抽离出来,在某些场景下使用泛型定义类,能够极大提高代码的可读性和可复用性。
泛型定义和使用
C#中的类、方法、接口、委托都支持泛型。
泛型类
泛型类的参数使用一对尖括号<>指定,泛型参数允许存在多个,使用逗号分隔。下面例子我们定义了一个泛型类,两个属性DataA和DataB的数据类型是通过泛型参数T和U指定的,这里类实例化时会传入具体的类型名。
class Container<T, U>
{
public T DataA { get; set; }
public U DataB { get; set; }
}
实例化泛型类时需要传入对应的类型参数,写法如下:
Container<int, string> container = new Container<int, string>();
container.DataA = 1;
container.DataB = "hello";
泛型方法
泛型方法的声明和泛型类差不多,也是增加一组泛型参数。下面例子中,我们在一个普通的类里声明了泛型方法。
class A
{
public void Foo<T, U>(T t, U u)
{
Console.WriteLine(u);
Console.WriteLine(t);
}
}
调用泛型方法时,也可以使用尖括号传入泛型参数。
A a = new A();
a.Foo<int, string>(1, "hello");
不过实际上,这里我们声明的Foo方法的两个类型参数T和U显然可以从其参数t和u类型中推断出来,因此调用该方法时我们可以不必传泛型参数,写作a.Foo(1, "hello");也是可以的,但不能自动推断的时候就必须显示的写出泛型参数了。
约束泛型类型
我们定义泛型时,可以使用where关键字对其进行限制,例如:必须继承某个类或接口,必须有无参构造方法,必须是引用类型或是值类型等,这些约束还可以组合使用。类、方法、接口、委托都支持泛型约束。
| 约束 | 说明 |
|---|---|
| where T : class | T必须是引用类型 |
| where T : struct | T必须是值类型 |
| where T : new() | T必须有无参构造函数,如果和其他约束组合使用,new()必须放在最后 |
| where T : 类名或接口名 | T必须派生自某类或接口 |
下面代码对类的泛型进行了约束,T类型必须继承Animal类。
public class Zoo<T> where T : Animal { }
多个约束可以组合使用,使用逗号分隔。
public class Zoo<T> where T : Animal, new() { }
下面代码对方法的泛型进行了约束,T类型必须有无参构造函数。
public static T GetInstance<T>() where T : new() { }
C#和Java在泛型上的区别
C#的泛型和Java的泛型虽然写法相似,但有本质上的不同。
Java中的泛型是采用类型擦除实现的,这种实现方式经常被诟病为“假泛型”,所谓的假泛型是一种编译期的语法糖,且只适用于引用类型,Java字节码中使用的还是Object类型,只不过编译器帮我们添加了类型转换的代码。而C#泛型是运行时级别支持的,且适用于引用类型和值类型,这导致两者在用法上有很多区别,C#支持很多Java程序员想都不敢想的写法!
下面例子代码我们编写了一个实例化泛型类型的代码,代码中直接使用new关键字实例化了一个泛型类型,这在Java中是无法实现的,Java实现类似功能只能传入一个类型对象,然后使用反射来实例化,写法十分扭曲。
public class ServiceFactory
{
public static T GetInstance<T>() where T : new()
{
T t = new T();
return t;
}
}
代码中我们使用了where T : new()约束保证T有无参构造函数。
调用该泛型方法:
StudentService s = ServiceFactory.GetInstance<StudentService>();