2.0版C#语言和公共语言运行时(CLR)中增长了泛型。泛型将类型参数的概念引入.NETFramework,类型参数使得设计以下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,经过使用泛型类型参数T,您能够编写其余客户端代码可以使用的单个类,而不致引入运行时强制转换或装箱操做的成本或风险,以下所示:node
//Declarethegenericclass.编程
publicclassGenericList<T>设计模式
{数组
voidAdd(Tinput){}安全
}app
classTestGenericListless
{ide
privateclassExampleClass{}函数
staticvoidMain()性能
{
//Declarealistoftypeint.
GenericList<int>list1=newGenericList<int>();
//Declarealistoftypestring.
GenericList<string>list2=newGenericList<string>();
//DeclarealistoftypeExampleClass.
GenericList<ExampleClass>list3=newGenericList<ExampleClass>();
}
}
泛型概述
在公共语言运行时和 C# 语言的早期版本中,通用化是经过在类型与通用基类型 Object 之间进行强制转换来实现的,泛型提供了针对这种限制的解决方案。 经过建立泛型类,您能够建立一个在编译时类型安全的集合。
使用非泛型集合类的限制能够经过编写一小段程序来演示,该程序使用 .NET Framework 类库中的 ArrayList 集合类。 ArrayList 是一个使用起来很是方便的集合类,无需进行修改便可用来存储任何引用或值类型。
// The .NET Framework 1.1 way to create a list:
System.Collections.ArrayList list1 = new System.Collections.ArrayList();
list1.Add(3);
list1.Add(105);
System.Collections.ArrayList list2 = new System.Collections.ArrayList();
list2.Add("It is raining in Redmond.");
list2.Add("It is snowing in the mountains.");
但这种方即是须要付出代价的。 添加到 ArrayList 中的任何引用或值类型都将隐式地向上强制转换为 Object。 若是项是值类型,则必须在将其添加到列表中时进行装箱操做,在检索时进行取消装箱操做。 强制转换以及装箱和取消装箱操做都会下降性能;在必须对大型集合进行循环访问的状况下,装箱和取消装箱的影响很是明显。 (附:装箱是将值类型转换为 object 类型或由此值类型实现的任一接口类型的过程。 当 CLR 对值类型进行装箱时,会将该值包装到 System.Object 内部,再将后者存储在托管堆上。 取消装箱将从对象中提取值类型。 装箱是隐式的;取消装箱是显式的。 装箱和取消装箱的概念是类型系统 C# 统一视图的基础,其中任一类型的值都被视为一个对象。相对于简单的赋值而言,装箱和取消装箱过程须要进行大量的计算。 对值类型进行装箱时,必须分配并构造一个新对象。 次之,取消装箱所需的强制转换也须要进行大量的计算。)
另外一个限制是缺乏编译时类型检查;由于 ArrayList 会将全部项都强制转换为 Object,因此在编译时没法防止客户端代码执行相似以下的操做:
System.Collections.ArrayList list = new System.Collections.ArrayList();
// Add an integer to the list.
list.Add(3);
// Add a string to the list. This will compile, but may cause an error later.
list.Add("It is raining in Redmond.");
int t = 0;
// This causes an InvalidCastException to be returned.
foreach (int x in list)
{
t += x;
}
尽管将字符串和 ints 组合在一个 ArrayList 中的作法在建立异类集合时是彻底可接受的,而且有时须要有意为之,但这种作法极可能产生编程错误,而且直到运行时才能检测到此错误。
在 C# 语言的 1.0 和 1.1 版本中,只能经过编写本身的特定于类型的集合来避免 .NET Framework 基类库集合类中的通用代码的危险。 固然,因为此类不可对多个数据类型重用,所以将丧失通用化的优势,而且您必须对要存储的每一个类型从新编写该类。
ArrayList 和其余类似类真正须要的是:客户端代码基于每一个实例指定这些类要使用的具体数据类型的方式。 这样将再也不须要向上强制转换为 T:System.Object,同时,也使得编译器能够进行类型检查。 换句话说,ArrayList 须要一个类型参数。 这正是泛型所能提供的。 在 N:System.Collections.Generic 命名空间的泛型 List<T> 集合中,向集合添加项的操做相似于如下形式:
// The .NET Framework 2.0 way to create a list
List<int> list1 = new List<int>();
// No boxing, no casting:
list1.Add(3);
// Compile-time error:
// list1.Add("It is raining in Redmond.");
对于客户端代码,与 ArrayList 相比,使用 List<T> 时添加的惟一语法是声明和实例化中的类型参数。虽然这种方式稍微增长了编码的复杂性,但好处是您能够建立一个比 ArrayList 更安全而且速度更快的列表,对于列表项是值类型的状况尤其如此。
在泛型类型或方法定义中,类型参数是客户端在实例化泛型类型的变量时指定的特定类型的占位符。 泛型类(如 泛型介绍(C# 编程指南) 中列出的 GenericList<T>)不能够像这样使用,由于它实际上并非一个类型,而更像是一个类型的蓝图。 若要使用 GenericList<T>,客户端代码必须经过指定尖括号中的类型参数来声明和实例化构造类型。 此特定类的类型参数能够是编译器识别的任何类型。 能够建立任意数目的构造类型实例,每一个实例使用不一样的类型参数,以下所示:
GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();
在每一个 GenericList<T> 实例中,类中出现的每一个 T 都会在运行时替换为相应的类型参数。 经过这种替换方式,咱们使用一个类定义建立了三个独立的类型安全的有效对象。 有关 CLR 如何执行此替换的更多信息,请参见运行时中的泛型(C# 编程指南)。
类型参数命名准则
public interface ISessionChannel<TSession> { /*...*/ }
public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class List<T> { /*...*/ }
public int IComparer<T>() { return 0; }
public delegate bool Predicate<T>(T item);
public struct Nullable<T> where T : struct { /*...*/ }
public interface ISessionChannel<TSession>
{
TSession Session { get; }
}
在定义泛型类时,能够对客户端代码可以在实例化类时用于类型参数的类型种类施加限制。 若是客户端代码尝试使用某个约束所不容许的类型来实例化类,则会产生编译时错误。 这些限制称为约束。 约束是使用 where 上下文关键字指定的。 下表列出了六种类型的约束:
约束 |
说明 |
T:结构 |
类型参数必须是值类型。 能够指定除 Nullable 之外的任何值类型。 有关更多信息,请参见 使用能够为 null 的类型(C# 编程指南)。 |
T:类 |
类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。 |
T:new() |
类型参数必须具备无参数的公共构造函数。 当与其余约束一块儿使用时,new() 约束必须最后指定。 |
T:<基类名> |
类型参数必须是指定的基类或派生自指定的基类。 |
T:<接口名称> |
类型参数必须是指定的接口或实现指定的接口。 能够指定多个接口约束。 约束接口也能够是泛型的。 |
T:U |
为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 |
使用约束的缘由
若是要检查泛型列表中的某个项以肯定它是否有效,或者将它与其余某个项进行比较,则编译器必须在必定程度上保证它须要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。 这种保证是经过对泛型类定义应用一个或多个约束得到的。 例如,基类约束告诉编译器:仅此类型的对象或今后类型派生的对象才可用做类型参数。 一旦编译器有了这个保证,它就可以容许在泛型类中调用该类型的方法。 约束是使用上下文关键字 where 应用的。 下面的代码示例演示可经过应用基类约束添加到 GenericList<T> 类(在泛型介绍(C# 编程指南)中)的功能。
public class Employee
{
private string name;
private int id;
public Employee(string s, int i)
{
name = s;
id = i;
}
public string Name
{
get { return name; }
set { name = value; }
}
public int ID
{
get { return id; }
set { id = value; }
}
}
public class GenericList<T> where T : Employee
{
private class Node
{
private Node next;
private T data;
public Node(T t)
{
next = null;
data = t;
}
public Node Next
{
get { return next; }
set { next = value; }
}
public T Data
{
get { return data; }
set { data = value; }
}
}
private Node head;
public GenericList() //constructor
{
head = null;
}
public void AddHead(T t)
{
Node n = new Node(t);
n.Next = head;
head = n;
}
public IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
public T FindFirstOccurrence(string s)
{
Node current = head;
T t = null;
while (current != null)
{
//The constraint enables access to the Name property.
if (current.Data.Name == s)
{
t = current.Data;
break;
}
else
{
current = current.Next;
}
}
return t;
}
}
约束使得泛型类可以使用 Employee.Name 属性,由于类型为 T 的全部项都保证是 Employee 对象或从 Employee 继承的对象。
能够对同一类型参数应用多个约束,而且约束自身能够是泛型类型,以下所示:
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
// ...
}
经过约束类型参数,能够增长约束类型及其继承层次结构中的全部类型所支持的容许操做和方法调用的数量。 所以,在设计泛型类或方法时,若是要对泛型成员执行除简单赋值以外的任何操做或调用 System.Object 不支持的任何方法,您将须要对该类型参数应用约束。
在应用 where T : class 约束时,避免对类型参数使用 == 和 != 运算符,由于这些运算符仅测试引用同一性而不测试值相等性。 即便在用做参数的类型中重载这些运算符也是如此。 下面的代码说明了这一点;即便 String 类重载 == 运算符,输出也为 false。
public static void OpTest<T>(T s, T t) where T : class
{
System.Console.WriteLine(s == t);
}
static void Main()
{
string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = sb.ToString();
OpTest<string>(s1, s2);
}
这种状况的缘由在于,编译器在编译时仅知道 T 是引用类型,所以必须使用对全部引用类型都有效的默认运算符。 若是必须测试值相等性,建议的方法是同时应用 where T : IComparable<T> 约束,并在将用于构造泛型类的任何类中实现该接口。
约束多个参数
能够对多个参数应用约束,并对一个参数应用多个约束,以下面的示例所示:
class Base { }
class Test<T, U>
where U : struct
where T : Base, new() { }
未绑定的类型参数
没有约束的类型参数(如公共类 SampleClass<T>{} 中的 T)称为未绑定的类型参数。未绑定的类型参数具备如下规则:
做为约束的类型参数
将泛型类型参数做为约束使用,在具备本身类型参数的成员函数必须将该参数约束为包含类型的类型参数时很是有用,以下示例所示:
class List<T>
{
void Add<U>(List<U> items) where U : T {/*...*/}
}
在上面的示例中,T 在 Add 方法的上下文中是一个类型约束,而在 List 类的上下文中是一个未绑定的类型参数。
类型参数还可在泛型类定义中用做约束。 请注意,必须在尖括号中声明此类型参数与任何其余类型的参数:
//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }
泛型类的类型参数约束的做用很是有限,由于编译器除了假设类型参数派生自 System.Object 之外,不会作其余任何假设。 在但愿强制两个类型参数之间的继承关系的状况下,可对泛型类使用参数类型约束。
泛型类封装不是特定于具体数据类型的操做。 泛型类最经常使用于集合,如连接列表、哈希表、堆栈、队列、树等。 像从集合中添加和移除项这样的操做都以大致上相同的方式执行,与所存储数据的类型无关。
对于大多数须要集合类的方案,推荐的方法是使用 .NET Framework 类库中所提供的类。 有关使用这些类的更多信息,请参见.NET Framework 类库中的泛型(C# 编程指南)。
通常状况下,建立泛型类的过程为:从一个现有的具体类开始,逐一将每一个类型更改成类型参数,直至达到通用化和可用性的最佳平衡。建立您本身的泛型类时,须要特别注意如下事项:
一般,可以参数化的类型越多,代码就会变得越灵活,重用性就越好。 可是,太多的通用化会使其余开发人员难以阅读或理解代码。
一条有用的规则是,应用尽量最多的约束,但仍使您可以处理必须处理的类型。 例如,若是您知道您的泛型类仅用于引用类型,则应用类约束。 这能够防止您的类被意外地用于值类型,并容许您对 T 使用 as 运算符以及检查空值。
因为泛型类能够做为基类使用,此处适用的设计注意事项与非泛型类相同。 请参见本主题后面有关从泛型基类继承的规则。
例如,若是您设计一个类,该类将用于建立基于泛型的集合中的项,则可能必须实现一个接口,如 IComparable<T>,其中 T 是您的类的类型。
有关简单泛型类的示例,请参见泛型介绍(C# 编程指南)。
类型参数和约束的规则对于泛型类行为有几方面的含义,特别是关于继承和成员可访问性。 您应当先理解一些术语,而后再继续进行。 对于泛型类 Node<T>,客户端代码可经过指定类型参数来引用该类,以便建立封闭式构造类型 (Node<int>)。 或者可让类型参数处于未指定状态(例如在指定泛型基类时)以建立开放式构造类型 (Node<T>)。 泛型类能够从具体的、封闭式构造或开放式构造基类继承:
class BaseNode { }
class BaseNodeGeneric<T> { }
// concrete type
class NodeConcrete<T> : BaseNode { }
//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }
//open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }
非泛型类(换句话说,即具体类)能够从封闭式构造基类继承,但没法从开放式构造类或类型参数继承,由于在运行时客户端代码没法提供实例化基类所需的类型参数。
//No error
class Node1 : BaseNodeGeneric<int> { }
//Generates an error
//class Node2 : BaseNodeGeneric<T> {}
//Generates an error
//class Node3 : T {}
从开放式构造类型继承的泛型类必须为任何未被继承类共享的基类类型参数提供类型变量,如如下代码所示:
class BaseNodeMultiple<T, U> { }
//No error
class Node4<T> : BaseNodeMultiple<T, int> { }
//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }
//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}
从开放式构造类型继承的泛型类必须指定约束,这些约束是基类型约束的超集或暗示基类型约束:
class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }
泛型类型可使用多个类型参数和约束,以下所示:
class SuperKeyType<K, V, U>
where U : System.IComparable<U>
where V : new()
{ }
开放式构造类型和封闭式构造类型能够用做方法参数:
void Swap<T>(List<T> list1, List<T> list2)
{
//code to swap items
}
void Swap(List<int> list1, List<int> list2)
{
//code to swap items
}
若是某个泛型类实现了接口,则能够将该类的全部实例强制转换为该接口。
泛型类是不变的。 也就是说,若是输入参数指定 List<BaseClass>,则当您尝试提供 List<DerivedClass> 时,将会发生编译时错误。
为泛型集合类或表示集合中项的泛型类定义接口一般颇有用。 对于泛型类,使用泛型接口十分可取,例如使用 IComparable<T> 而不使用 IComparable,这样能够避免值类型的装箱和取消装箱操做。 .NET Framework 类库定义了若干泛型接口,以用于 System.Collections.Generic 命名空间中的集合类。
将接口指定为类型参数的约束时,只能使用实现此接口的类型。 下面的代码示例显示从 SortedList<T> 类派生的 GenericList<T> 类。 有关更多信息,请参见 泛型介绍(C# 编程指南)。 SortedList<T> 添加约束 where T : IComparable<T>。 这将使 SortedList<T> 中的 BubbleSort 方法可以对列表元素使用泛型 CompareTo 方法。 在此示例中,列表元素为简单类,即实现 Person 的 IComparable<Person>。
//Type parameter T in angle brackets.
public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
protected Node head;
protected Node current = null;
// Nested class is also generic on T
protected class Node
{
public Node next;
private T data; //T as private member datatype
public Node(T t) //T used in non-generic constructor
{
next = null;
data = t;
}
public Node Next
{
get { return next; }
set { next = value; }
}
public T Data //T as return type of property
{
get { return data; }
set { data = value; }
}
}
public GenericList() //constructor
{
head = null;
}
public void AddHead(T t) //T as method parameter type
{
Node n = new Node(t);
n.Next = head;
head = n;
}
// Implementation of the iterator
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
// IEnumerable<T> inherits from IEnumerable, therefore this class
// must implement both the generic and non-generic versions of
// GetEnumerator. In most cases, the non-generic method can
// simply call the generic method.
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class SortedList<T> : GenericList<T> where T : System.IComparable<T>
{
// A simple, unoptimized sort algorithm that
// orders list elements from lowest to highest:
public void BubbleSort()
{
if (null == head || null == head.Next)
{
return;
}
bool swapped;
do
{
Node previous = null;
Node current = head;
swapped = false;
while (current.next != null)
{
// Because we need to call this method, the SortedList
// class is constrained on IEnumerable<T>
if (current.Data.CompareTo(current.next.Data) > 0)
{
Node tmp = current.next;
current.next = current.next.next;
tmp.next = current;
if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
current = current.next;
}
}
} while (swapped);
}
}
// A simple class that implements IComparable<T> using itself as the
// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
string name;
int age;
public Person(string s, int i)
{
name = s;
age = i;
}
// This will cause list elements to be sorted on age values.
public int CompareTo(Person p)
{
return age - p.age;
}
public override string ToString()
{
return name + ":" + age;
}
// Must implement Equals.
public bool Equals(Person p)
{
return (this.age == p.age);
}
}
class Program
{
static void Main()
{
//Declare and instantiate a new generic SortedList class.
//Person is the type argument.
SortedList<Person> list = new SortedList<Person>();
//Create name and age values to initialize Person objects.
string[] names = new string[]
{
"Franscoise",
"Bill",
"Li",
"Sandra",
"Gunnar",
"Alok",
"Hiroyuki",
"Maria",
"Alessandro",
"Raul"
};
int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };
//Populate the list.
for (int x = 0; x < 10; x++)
{
list.AddHead(new Person(names[x], ages[x]));
}
//Print out unsorted list.
foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with unsorted list");
//Sort the list.
list.BubbleSort();
//Print out sorted list.
foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with sorted list");
}
}
可将多重接口指定为单个类型上的约束,以下所示:
class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}
一个接口可定义多个类型参数,以下所示:
interface IDictionary<K, V>
{
}
适用于类的继承规则一样适用于接口:
interface IMonth<T> { }
interface IJanuary : IMonth<int> { } //No error
interface IFebruary<T> : IMonth<int> { } //No error
interface IMarch<T> : IMonth<T> { } //No error
//interface IApril<T> : IMonth<T, U> {} //Error
若是泛型接口为逆变的,即仅使用其类型参数做为返回值,则此泛型接口能够从非泛型接口继承。 在 .NET Framework 类库中,IEnumerable<T> 从 IEnumerable 继承,由于 IEnumerable<T> 只在 GetEnumerator 的返回值和 Current 属性 getter 中使用 T。
具体类能够实现已关闭的构造接口,以下所示:
interface IBaseInterface<T> { }
class SampleClass : IBaseInterface<string> { }
只要类参数列表提供了接口必需的全部参数,泛型类即可以实现泛型接口或已关闭的构造接口,以下所示:
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }
class SampleClass1<T> : IBaseInterface1<T> { } //No error
class SampleClass2<T> : IBaseInterface2<T, string> { } //No error
对于泛型类、泛型结构或泛型接口中的方法,控制方法重载的规则相同。 有关更多信息,请参见泛型方法(C# 编程指南)。
泛型方法是使用类型参数声明的方法,以下所示:
static void Swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
下面的代码示例演示一种使用 int 做为类型参数的方法调用方式:
public static void TestSwap()
{
int a = 1;
int b = 2;
Swap<int>(ref a, ref b);
System.Console.WriteLine(a + " " + b);
}
也能够省略类型参数,编译器将推断出该参数。 下面对 Swap 的调用等效于前面的调用:
Swap(ref a, ref b);
相同的类型推理规则也适用于静态方法和实例方法。编译器可以根据传入的方法实参推断类型形参;它没法仅从约束或返回值推断类型形参。所以,类型推理不适用于没有参数的方法。类型推理在编译时、编译器尝试解析重载方法签名以前进行。编译器向共享相同名称的全部泛型方法应用类型推理逻辑。在重载解析步骤中,编译器仅包括类型推理取得成功的那些泛型方法。
在泛型类中,非泛型方法能够访问类级别类型参数,以下所示:
class SampleClass<T>
{
void Swap(ref T lhs, ref T rhs) { }
}
若是定义采用相同类型参数做为包含类的泛型方法,编译器将生成警告 CS0693,由于在方法范围内为内部 T 提供的参数隐藏了为外部 T 提供的参数。若是须要使用其余类型参数(而不是实例化类时提供的类型参数)来灵活地调用泛型类方法,请考虑为方法的类型参数提供另外一个标识符,以下面示例的 GenericList2<T> 中所示。
class GenericList<T>
{
// CS0693
void SampleMethod<T>() { }
}
class GenericList2<T>
{
//No warning
void SampleMethod<U>() { }
}
使用约束对方法中的类型参数启用更专门的操做。此版本的 Swap<T> 如今名为 SwapIfGreater<T>,它只能与实现 IComparable<T> 的类型参数一块儿使用。
void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>
{
T temp;
if (lhs.CompareTo(rhs) > 0)
{
temp = lhs;
lhs = rhs;
rhs = temp;
}
}
泛型方法可使用许多类型参数进行重载。 例如,下列方法能够所有位于同一个类中:
void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }
在 C# 2.0 以及更高版本中,下限为零的一维数组自动实现 IList<T>。这使您能够建立可以使用相同代码循环访问数组和其余集合类型的泛型方法。此技术主要对读取集合中的数据颇有用。IList<T> 接口不能用于在数组中添加或移除元素。若是尝试对此上下文中的数组调用 IList<T> 方法(例如 RemoveAt),则将引起异常。
下面的代码示例演示带有 IList<T> 输入参数的单个泛型方法如何同时循环访问列表和数组,本例中为整数数组。
class Program
{
static void Main()
{
int[] arr = { 0, 1, 2, 3, 4 };
List<int> list = new List<int>();
for (int x = 5; x < 10; x++)
{
list.Add(x);
}
ProcessItems<int>(arr);
ProcessItems<int>(list);
}
static void ProcessItems<T>(IList<T> coll)
{
// IsReadOnly returns True for the array and False for the List.
System.Console.WriteLine
("IsReadOnly returns {0} for this collection.",
coll.IsReadOnly);
// The following statement causes a run-time exception for the
// array, but not for the List.
//coll.RemoveAt(4);
foreach (T item in coll)
{
System.Console.Write(item.ToString() + " ");
}
System.Console.WriteLine();
}
}
委托 能够定义本身的类型参数。引用泛型委托的代码能够指定类型参数以建立已关闭的构造类型,就像实例化泛型类或调用泛型方法同样,以下例所示:
public delegate void Del<T>(T item);
public static void Notify(int i) { }
Del<int> m1 = new Del<int>(Notify);
C# 2.0 版具备称为方法组转换的新功能,此功能适用于具体委托类型和泛型委托类型,并使您可使用以下简化的语法写入上一行:
Del<int> m2 = Notify;
在泛型类内部定义的委托使用泛型类类型参数的方式能够与类方法所使用的方式相同。
class Stack<T>
{
T[] items;
int index;
public delegate void StackDelegate(T[] items);
}
引用委托的代码必须指定包含类的类型变量,以下所示:
private static void DoWork(float[] items) { }
public static void TestStack()
{
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate d = DoWork;
}
根据典型设计模式定义事件时,泛型委托尤为有用,由于发送方参数能够为强类型,再也不须要强制转换成 Object,或反向强制转换。
delegate void StackEventHandler<T, U>(T sender, U eventArgs);
class Stack<T>
{
public class StackEventArgs : System.EventArgs { }
public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;
protected virtual void OnStackChanged(StackEventArgs a)
{
stackEvent(this, a);
}
}
class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}
public static void Test()
{
Stack<double> s = new Stack<double>();
SampleClass o = new SampleClass();
s.stackEvent += o.HandleStackChange;
}
在泛型类和泛型方法中产生的一个问题是,在预先未知如下状况时,如何将默认值分配给参数化类型 T:
给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。解决方案是使用 default 关键字,此关键字对于引用类型会返回 null,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或 null 的每一个结构成员,具体取决于这些结构是值类型仍是引用类型。对于能够为 null 的值类型,默认返回 System.Nullable<T>,它像任何结构同样初始化。
如下来自 GenericList<T> 类的示例显示了如何使用 default 关键字。 有关更多信息,请参见泛型概述。
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Test with a non-empty list of integers.
GenericList<int> gll = new GenericList<int>();
gll.AddNode(5);
gll.AddNode(4);
gll.AddNode(3);
int intVal = gll.GetLast();
// The following line displays 5.
System.Console.WriteLine(intVal);
// Test with an empty list of integers.
GenericList<int> gll2 = new GenericList<int>();
intVal = gll2.GetLast();
// The following line displays 0.
System.Console.WriteLine(intVal);
// Test with a non-empty list of strings.
GenericList<string> gll3 = new GenericList<string>();
gll3.AddNode("five");
gll3.AddNode("four");
string sVal = gll3.GetLast();
// The following line displays five.
System.Console.WriteLine(sVal);
// Test with an empty list of strings.
GenericList<string> gll4 = new GenericList<string>();
sVal = gll4.GetLast();
// The following line displays a blank line.
System.Console.WriteLine(sVal);
}
}
// T is the type of data stored in a particular instance of GenericList.
public class GenericList<T>
{
private class Node
{
// Each node has a reference to the next node in the list.
public Node Next;
// Each node holds a value of type T.
public T Data;
}
// The list is initially empty.
private Node head = null;
// Add a node at the beginning of the list with t as its data value.
public void AddNode(T t)
{
Node newNode = new Node();
newNode.Next = head;
newNode.Data = t;
head = newNode;
}
// The following method returns the data value stored in the last node in
// the list. If the list is empty, the default value for type T is
// returned.
public T GetLast()
{
// The value of temp is returned as the value of the method.
// The following declaration initializes temp to the appropriate
// default value for type T. The default value is returned if the
// list is empty.
T temp = default(T);
Node current = head;
while (current != null)
{
temp = current.Data;
current = current.Next;
}
return temp;
}
}
}
将泛型类型或方法编译为 Microsoft 中间语言 (MSIL) 时,它包含将其标识为具备类型参数的元数据。 泛型类型的 MSIL 的使用因所提供的类型参数是值类型仍是引用类型而不一样。
第一次用值类型做为参数来构造泛型类型时,运行时会建立专用泛型类型,将提供的参数代入到 MSIL 中的适当位置。 对于每一个用做参数的惟一值类型,都会建立一次专用泛型类型。
例如,假设您的程序代码声明了一个由整数构造的堆栈:
Stack<int> stack;
在此位置,运行时生成 Stack<T> 类的专用版本,并相应地用整数替换其参数。 如今,只要程序代码使用整数堆栈,运行时就会重用生成的专用 Stack<T> 类。 在下面的示例中,建立了整数堆栈的两个实例,它们共享 Stack<int> 代码的单个实例:
Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();
可是,假定在代码中的另外一个位置建立了使用不一样值类型(好比 long 或用户定义的结构)做为其参数的另外一个 Stack<T> 类。 所以,运行时将生成另外一个版本的泛型类型,并在 MSIL 中的适当位置替换 long。 因为每一个专用泛型类自己就包含值类型,所以再也不须要转换。
对于引用类型,泛型的工做方式略有不一样。 第一次使用任何引用类型构造泛型类型时,运行时会建立专用泛型类型,用对象引用替换 MSIL 中的参数。 而后,每次使用引用类型做为参数来实例化构造类型时,不管引用类型的具体类型是什么,运行时都会重用之前建立的泛型类型的专用版本。 之因此能够这样,是由于全部引用的大小相同。
例如,假设您有两个引用类型:一个 Customer 类和一个 Order 类,而且同时假设您建立了一个 Customer 类型的堆栈:
class Customer { }
class Order { }
Stack<Customer> customers;
此时,运行时将生成 Stack<T> 类的一个专用版本,该版本存储稍后将填写的对象引用,而不是存储数据。 假设下一行代码建立另外一个引用类型的堆栈,该堆栈名为 Order:
Stack<Order> orders = new Stack<Order>();
不一样于值类型,对于 Order 类型不建立 Stack<T> 类的另外一个专用版本。 而是建立 Stack<T> 类的一个专用版本实例,并将 orders 变量设置为引用它。 假设接下来您遇到一行建立 Customer 类型堆栈的代码:
customers = new Stack<Customer>();
与前面使用 Order 类型建立的 Stack<T> 类同样,建立了专用 Stack<T> 类的另外一个实例。 包含在其中的指针设置为引用 Customer 类型大小的内存区域。 由于引用类型的数量会随程序的不一样而大幅变化,C# 泛型实现将编译器为引用类型的泛型类建立的专用类的数量减少到一个,从而大幅减少代码量。
此外,使用值类型或引用类型参数实例化泛型 C# 类时,反射能够在运行时查询它,而且能够肯定它的实际类型及其类型参数。
由于公共语言运行时 (CLR) 可以在运行时访问泛型类型信息,因此可使用反射获取关于泛型类型的信息,方法与用于非泛型类型的方法相同。 有关更多信息,请参见运行时中的泛型(C# 编程指南)。
在 .NET Framework 2.0 中,有几个新成员添加到了 Type 类中,用以启用泛型类型的运行时信息。 请参见有关这些类的文档来了解有关如何使用这些方法和属性的更多信息。System.Reflection.Emit 命名空间还包含支持泛型的新成员。 请参见 如何:用反射发出定义泛型类型。
有关泛型反射中使用的术语的固定条件列表,请参见 IsGenericType 属性备注。
System.Type 成员名称 |
说明 |
若是类型为泛型,则返回 true。 |
|
返回 Type 对象数组,这些对象表示为构造类型提供的类型变量,或泛型类型定义的类型参数。 |
|
返回当前构造类型的基础泛型类型定义。 |
|
返回表示当前泛型类型参数约束的 Type 对象的数组。 |
|
若是类型或其任意封闭类型或方法包含没有被提供特定类型的类型参数,则返回 true。 |
|
获取 GenericParameterAttributes 标志的组合,这些标志描述当前泛型类型参数的特殊约束。 |
|
对于表示类型参数的 Type 对象,获取类型参数在声明该类型参数的泛型类型定义或泛型方法定义的类型参数列表中的位置。 |
|
获取一个值,该值指示当前 Type 是表示泛型类型定义的类型参数,仍是泛型方法定义的类型参数。 |
|
获取一个值,该值指示当前 Type 是否表示能够用来构造其余泛型类型的泛型类型定义。 若是类型表示泛型类型的定义,则返回 true。 |
|
返回定义当前泛型类型参数的泛型方法;若是类型参数不是由泛型方法定义的,则返回空值。 |
|
用类型数组的元素替代当前泛型类型定义的类型参数,并返回表示结果构造类型的 Type 对象。 |
此外,MethodInfo 类中还添加了新成员以启用泛型方法的运行时信息。 有关泛型方法反射中使用的术语的固定条件列表,请参见 IsGenericMethod 属性备注。
System.Reflection.MemberInfo 成员名称 |
说明 |
若是方法为泛型,则返回 true。 |
|
返回 Type 对象数组,这些对象表示构造泛型方法的类型变量,或泛型方法定义的类型参数。 |
|
返回当前构造方法的基础泛型方法定义。 |
|
若是方法或其任意封闭类型包含没有被提供特定类型的任何类型参数,则返回 true。 |
|
若是当前 MethodInfo 表示泛型方法的定义,则返回 true。 |
|
用类型数组的元素替代当前泛型方法定义的类型参数,并返回表示结果构造方法的 MethodInfo 对象。 |
特性能够应用于泛型类型中,方式与应用于非泛型类型相同。 有关应用特性的更多信息,请参见 特性(C# 和 Visual Basic)。
自定义特性只容许引用开放泛型类型(未提供类型参数的泛型类型)和封闭构造泛型类型(为全部类型参数提供参数)。
下面的示例使用此自定义特性:
class CustomAttribute : System.Attribute
{
public System.Object info;
}
特性能够引用开放式泛型类型:
public class GenericClass1<T> { }
[CustomAttribute(info = typeof(GenericClass1<>))]
class ClassA { }
使用数目适当的若干个逗号指定多个类型参数。 在此示例中,GenericClass2 有两个类型参数:
public class GenericClass2<T, U> { }
[CustomAttribute(info = typeof(GenericClass2<,>))]
class ClassB { }
特性能够引用封闭式构造泛型类型:
public class GenericClass3<T, U, V> { }
[CustomAttribute(info = typeof(GenericClass3<int, double, string>))]
class ClassC { }
引用泛型类型参数的特性将致使编译时错误:
//[CustomAttribute(info = typeof(GenericClass3<int, T, string>))] //Error
class ClassD<T> { }
不能从 Attribute 继承泛型类型:
//public class CustomAtt<T> : System.Attribute {} //Error
若要在运行时得到有关泛型类型或类型参数的信息,可使用 System.Reflection 的方法。 有关更多信息,请参见泛型和反射(C# 编程指南)