要创建泛型类,只需要在类定义中包含尖括号语法:

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;
}