泛型
泛型(generic)是C#中一个重要概念,简单地说,泛型是编写一个类可以针对不同的类型。即通过参数化类型来实现同一份代码上操作多种数据类型。泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用。C#是在2.0 版本中开始引入泛型的。
泛型的基本使用
考虑这样一个问题:如果要定义一个含有多个元素的列表集合List
,有加入。删除、查找等功能,但是为了表示元素的类型可以是各种类型(如int
,string
,Point
,Person
等),如果不适用泛型,则有两种方法,一是针对每一种类型写一遍,如:
class IntList{
void Add(int a){
...} bool Remove(int a){
...}}
class StringList{
void Add(string a){
...} bool Remove(string a){
...}}
class PointList{
void Add(Point a){
...} bool Remove(Point a){
...}}
class PersonList{
void Add(Person a){
...} bool Remove(Person a){
...}}
这样显然太麻烦了。另一种写法是写一个针对object
元素的集合类,如:
class List{
void Add(object a){
...} bool Remove(object a){
...}}
后一种写法解决了object
可以针对任意类型的问题,但是又将类型信息去掉了,如果一个int的集合中加入string
对象,系统并不知道。
解决这种问题的方法就是使用泛型,即给上面的类加一个“类型参数”,从而表示其中元素的类型,这样既能针对不同的类型,同时有指明类型。在定义类型时,使用尖括号来表示类型参数:
class List<T>
{
void Add(T a){
...}
bool Remove(T a){
...}
}
这里T就是类型参数,它表示任意类型。在实际使用时,只需具体指明所使用的类型,如:
List<int> list1 = new List<int>(); list1.Add(5);.
List<string> list2 = new List<string>(); list2.Add("abc");
List<Point> list3 = new List<Point>(); list3.Add(new Point());
自定义泛型
1.泛型类的声明
类定义可以通过在类名后添加用尖括号括起来的类型参数名称列表来指定一组类型参数。类型参数可用于在类声明体中定义类成员。例如:
public class Pair<TFirst, TSecond>
{
public TFirst First;
public TSecond Second;
}
当使用泛型类时,必须为每个类型参数提供类型实参:
Pair<int, string> pair = new Pair<int, string> (){
}
int i = pair.First; // First is int
string s = pair.Second; // Second is string
按照习惯,泛型的类型参数以大写字母T开始,如果只有一个类型参数,可以只有一个大写字母T。
例:GenericStark.cs
自定义一个泛型的栈类。
using System;
public class MyStark<T>
{
private T[] buffer;
private int index = 0;
private int size;
public MyStark(int size = 100) {
buffer = new T[size];
this.size = size;
}
public void Push(T data) {
if (index >= size) throw new Exception();
buffer[index++] = data;
}
public T Pop() {
if (index == 0) throw new Exception();
return buffer[--index];
}
public bool IsEmpty() {
return index == 0;
}
}
class Program
{
static void Main(){
MyStark<string> stark = new MyStark<string>();
stark.Push("aaa");
stark.Push("bbbb");
stark.Push("ccccc");
while (!stark.IsEmpty()) {
string a = stark.Pop();
Console.WriteLine(a);
}
}
}
2.泛型结构、接口和泛型方法
对于泛型的结构、接口等定义与泛型类的定义相似。对于泛型方法,则是将参数型放到方法名的后面,例如:
private static Random rnd = new Random();
static T RandomOneOf<T>(T a, T b) {
if (rnd.Next(2) == 0) return a;
return b;
}
在调用泛型方法时,可以加上实际的参数类型,但是在多数情况下,编译器能推断出类型,这时尖括号及实际类型可以省略不写。以下两种写法都可以:
string s1 = RandomOneOf<string>("aaa", "bbb");
string s2 = RandomOneOf("ccc", "ddd");
例:GenericMethod.cs
自定义泛型方法。
using System;
class GenericMethod
{
public static void Shuffle<T>(T[] array) {
Random rnd = new Random();
for (int i = 1; i < array.Length; i++) {
Swap<T>(array, i, rnd.Next(0, i));
}
}
static void Swap<T>(T[] array, int indexA, int indexB) {
T temp = array[indexA];
array[indexA] = array[indexB];
array[indexB] = temp;
}
}
class Program
{
static void Main(string[] args) {
// 初始化牌局
int[] array = new int[54];
for (int i = 0; i < array.Length; i++) {
array[i] = i;
}
// 洗牌
GenericMethod.Shuffle<int>(array);
// 显示
foreach (int n in array) {
Console.Write(n + " ");
}
}
}
3.类型参数的约束
在使用泛型时,有时需要使类型参数满足某些条件,比如要求是某个1类及其子类,这时则需要使用where
关键字进行类型参数的约束。如:
class MyList<T>where T:Person
表示类型参数是Person
类(及其子类)。
常见的约束有以下其中形式:
在实际编程中,如果程序中要使用new T()
这样的方式来创建一个对象,则要加上where T : new ()
这样的约束。如果程序要使用针对T类型的null
,则要求使用T : class
这样的约束。如果类型可能是类也可能是值类型,可以使用default
运算符表示默认值(null
,0
,false
),如:
T a = default(T);
可以说,这个default
就是专门用来解决泛型的默认值问题的。
在C#7.0 版本中,可以写的更简单:
T a = default;
4.泛型接口中的out和in类型参数
泛型接口在定义时与泛型类的定义相似,但是在很多时候,要考虑到接口的广泛适应性,还要用到两个修饰符,out
和in
。如果参数类型用out修饰,则该类型只能用作方法的返回值,不能用作方法的参数;如果用in修饰则该类型只能用作方法的参数,不能用作方法的返回值。用out
修饰的叫作协变(covariant);用in
修饰的叫作逆变(contravariant);如果没有out
也没用in
,则称类型参数为固定的(invariant)。
在下面示例中,
interface C <out X, in Y, Z>
{
X M(Y y);
Z P{
get; set;}
}
X
为协变,Y
为逆变,而Z
为固定的。协变与逆变主要是要解决子类与父类的转换问题。