要创建泛型类,只需要在类定义中包含尖括号语法:
class MyGenericClass<T> {...}
其中T可以是任意标识符,只要遵循通常的C#命名规则即可,但一般只使用T。泛型类可在其定义中包含任意多个类型参数,参数之间用逗号分隔,例如:
class MyGeneriaClass<T1, T2, T3> {...}
class MyGenericClass<T1, T2, T3>
{
private T1 innerT1Object;
public MyGenericClass(T1 item)
{
innerT1Object = item;//不能写成 innerT1Object = new T1();
}
public T1 InnerT1Object
{
get { return innerT1Object;}
}
}
其中,类型T1的对象可以传递给构造函数,只能通过InnerT1Object属性对这个对象进行只读访问。注意,不能假定为类提供了什么类型。
因为我们并不知道T1是什么,也就不能使用它的构造函数,它甚至可能没有构造函数,或者没有可公共访问的默认构造函数。如果不涉及高级技术的复杂代码,则只能对T1进行如下假设:可以把它看成继承自System.Object的类型或可以封箱到System.Object中的类型。
显然,这意味着不能对这个类型的实例进行非常有趣的操作,或者对为MyGenericClass泛型类提供的其他类型进行有趣的操作。不使用反射技术,就只能使用下面的代码:
public string GetAllTypesAsString()
{
return " T1 = " + typeof(T1).ToString() + ", T2 = " + typeof(T2).ToString() + ", T3 = " + typeof(T3).ToString();
}
可以做一些其他工作,尤其是对集合进行操作,因为处理对象组是非常简单的,不需要对对象类型进行任何假设,这是为什么存在本章前面介绍的泛型集合类的一个原因。
另一个需要注意的限制是,在比较为泛型类型提供的类型值和null时,只能使用运算符==或!=:
public bool Compare(T1 op1, T2 op2)
{
if(op1 != null && op2 != null)//if (op1 == op2)这段代码在编译时就会出现错误
{
return true;
}
else
{
return false;
}
}
1 default关键字
要确定用于创建泛型类实例的类型,需要了解它们是引用类型还是值类型。如果T1是值类型,则innerT1Object不能取null值,所以这段代码不会编译。
使用default关键字可以有效解决这一问题:
public MyGenericClass()
{
innerT1Object = default(T1);
}
其结果是,如果innerT1Object是引用类型,就给它赋予null值;如果它是值类型,就给它赋予默认值。对于数字类型,这个默认值是0;而结构根据其各个成员的类型,以相同的方式初始化为0或null。default关键字允许对必须使用的类型执行更多操作,但为了更进一步,还需要限制所提供的类型。
2 约束类型
前面用于泛型类的类型称为无绑定类型,因为没有对他们进行任何约束。而通过约束类型,可以限制用于实例化泛型类的类型。在类定义中,这可以使用where关键字实现:
class MyGeneriaClass<T> where T : constraint {...}
其中constraint定义了约束。可以用这样的方式提供多种约束,各个约束之间用逗号分开:
class MyGeneriaClass<T> where T : constraint1,constraint2 {...}
还可以使用多个where语句,定义泛型类需要的任意类型或所有类型上的约束:
class MyGeneriaClass<T1, T2> where T1 : constraint1 where T2 : constraint2 {...}
约束必须出现在继承说明符的后面:
class MyGenericClass<T1, T2> : MyBaseClass, IMyInterface where T1 : constraint1 where T2 : constraint2 {...}
下面给出了一些可用的约束:
约束 | 定义 | 用法示例 |
struct | 类型必须是值类型 | 在类中,需要值类型才能起作用,例如,T类型的成员变量是0,表示某种含义 |
class | 类型必须是引用类型 | 在类中,需要引用类型才能起作用,例如,T类型的成员变量是努力了,表示某种含义 |
base-class | 类型必须是基类或继承自基类。可以根据这个约束提供任意类名 | 在类中,需要继承自基类的某种基本功能,才能起作用 |
interface | 类型必须是接口或者实现了接口 | 在类中,需要接口公开的某种基本功能,才能起作用 |
new() | 类型必须有一个公共的无参构造函数 | 在类中,需要能实例化T类型的变量,例如在构造函数中实例化,如果使用new()作为约束,它就必须是类型指定的最后一个约束 |
可以通过base-class约束,把一个类型参数用作另一个类型参数的约束:
class MyGenericClass<T1, T2> where T2 : T1 {...}
其中,T2必须与T1的类型相同,或继承自T1.这成为罗类型约束,表示一个泛型类型参数用作另一个类型参数的约束。
3 从泛型类中继承
首先,如果某个类型所继承的基类型中受到了约束,该类型就不能“解除约束”。也就是说,类型T在所继承的基类型中使用时,该类型必须受到至少与基类型相同的约束。
另外,如果继承自一个泛型类型,就必须提供所有必须的类型信息,这可以使用其他泛型类型参数的格式来提供。
如果给泛型类型提供了参数,就可以称该类型是“关闭的”,如果没有提供参数的话,那这个类型就是“打开的”。
4 泛型运算符
在C#中,可以像其他方法一样进行运算符的重写,也可以在泛型类中实现此类重写。例如,可在Farm<T>中定义如下隐式转换运算符:
public static implicit operator List<Animal>(Farm<T> farm)
{
List<Animal> result = new List<Animal>();
foreach (T animal in farm)
{
result.Add(Animal);
}
return result;
}
如果,有必要的话,可以在Farm<T>中把Animal对象直接作为List<Animal>来访问:
public static Farm<T> operator +(Farm<T> farm1, List<T> farm2)
{
Farm<T> result = new Farm<T>();
foreach (T animal in farm1)
{
result.Animal.Add(animal);
}
foreach (T animal in farm2)
{
if (!result.Animal.Contains(animal))
{
result.Animals.Add(animal);
}
}
return result;
}
public static Farm<T> operator + (List<T> farm1, Farm<T> farm2) => farm2 + farm1;
接着可以添加Farm<Animal>和Farm<Cow>的实例:
Farm<Animal> newFarm = farm + dairyFarm;
在这行代码中,dairyFarm(Farm<Cow>的实例)隐式转换为List<Animal>,List<Animal>可在Farm<T>中由重载运算符+使用。
为了解决Farm<Cow>转换为Farm<Animal>导致汇总失败的问题,可以使用下面的转换运算符来解决这一问题:
public static implicit operator Farm<Animal>(Farm<T> farm)
{
Farm<Animal> result = new Farm <Animal>();
foreach (T animal in farm)
{
result.Animal.Add(animal);
}
return result;
}
使用这个运算符,Farm<T>的实例(如Farm<Cow>)就可以转换为Farm<Animal>的实例,这解决了上面的问题。
5 泛型结构
可以用与泛型类相同的方式来创建泛型结构:
public struct Mystruct<T1, T2>
{
public T1 item1;
public T2 item2;
}