MSDN中泛型的相关介绍.doc_第1页
MSDN中泛型的相关介绍.doc_第2页
MSDN中泛型的相关介绍.doc_第3页
MSDN中泛型的相关介绍.doc_第4页
MSDN中泛型的相关介绍.doc_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

泛型(C# 编程指南)泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,通过使用泛型类型参数 T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险,如下所示:C#复制代码/ Declare the generic classpublic class GenericList void Add(T input) class TestGenericList private class ExampleClass static void Main() / Declare a list of type int GenericList list1 = new GenericList(); / Declare a list of type string GenericList list2 = new GenericList(); / Declare a list of type ExampleClass GenericList list3 = new GenericList(); 泛型概述 使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。 泛型最常见的用途是创建集合类。 .NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类,如 System.Collections 命名空间中的 ArrayList。 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。 可以对泛型类进行约束以访问特定数据类型的方法。 关于泛型数据类型中使用的类型的信息可在运行时通过反射获取。泛型介绍(C# 编程指南)泛型类和泛型方法同时具备可重用性、类型安全和效率,这是非泛型类和非泛型方法无法具备的。泛型通常用在集合和在集合上运行的方法中。.NET Framework 2.0 版类库提供一个新的命名空间 System.Collections.Generic,其中包含几个新的基于泛型的集合类。建议面向 2.0 版的所有应用程序都使用新的泛型集合类,而不要使用旧的非泛型集合类,如 ArrayList。有关更多信息,请参见 .NET Framework 类库中的泛型(C# 编程指南)。当然,也可以创建自定义泛型类型和方法,以提供自己的通用解决方案,设计类型安全的高效模式。下面的代码示例演示一个用于演示用途的简单泛型链接列表类。(大多数情况下,建议使用 .NET Framework 类库提供的 List 类,而不要自行创建类。)在通常使用具体类型来指示列表中所存储项的类型时,可使用类型参数 T。其使用方法如下: 在 AddHead 方法中作为方法参数的类型。 在 Node 嵌套类中作为公共方法 GetNext 和 Data 属性的返回类型。 在嵌套类中作为私有成员数据的类型。注意,T 可用于 Node 嵌套类。如果使用具体类型实例化 GenericList(例如,作为 GenericList),则所有的 T 都将被替换为 int。C#复制代码/ type parameter T in angle bracketspublic class GenericList / The nested class is also generic on T private class Node / T used in non-generic constructor public Node(T t) next = null; data = t; private Node next; public Node Next get return next; set next = value; / T as private member data type private T data; / T as return type of property public T Data get return data; set data = value; private Node head; / constructor public GenericList() head = null; / T as method parameter type: public void AddHead(T t) Node n = new Node(t); n.Next = head; head = n; public IEnumerator GetEnumerator() Node current = head; while (current != null) yield return current.Data; current = current.Next; 下面的代码示例演示客户端代码如何使用泛型 GenericList 类来创建整数列表。只需更改类型参数,即可方便地修改下面的代码示例,创建字符串或任何其他自定义类型的列表:C#复制代码class TestGenericList static void Main() / int is the type argument GenericList list = new GenericList(); for (int x = 0; x 10; x+) list.AddHead(x); foreach (int i in list) System.Console.Write(i + ); System.Console.WriteLine(nDone); 泛型的优点(C# 编程指南)在公共语言运行库和 C# 语言的早期版本中,通用化是通过在类型与通用基类型 Object 之间进行强制转换来实现的,泛型提供了针对这种限制的解决方案。通过创建泛型类,您可以创建一个在编译时类型安全的集合。使用非泛型集合类的限制可以通过编写一小段程序来演示,该程序利用 .NET Framework 基类库中的 ArrayList 集合类。ArrayList 是一个使用起来非常方便的集合类,无需进行修改即可用来存储任何引用或值类型。 C#复制代码/ 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。如果项是值类型,则必须在将其添加到列表中时进行装箱操作,在检索时进行取消装箱操作。强制转换以及装箱和取消装箱操作都会降低性能;在必须对大型集合进行循环访问的情况下,装箱和取消装箱的影响非常明显。另一个限制是缺少编译时类型检查;因为 ArrayList 将把所有项都强制转换为 Object,所以在编译时无法防止客户端代码执行以下操作:C#复制代码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 需要一个 type parameter。这正是泛型所能提供的。在 N:System.Collections.Generic 命名空间的泛型 List 集合中,向该集合添加项的操作类似于以下形式:C#复制代码/ The .NET Framework 2.0 way to create a listList list1 = new List();/ No boxing, no casting:list1.Add(3);/ Compile-time error:/ list1.Add(It is raining in Redmond.);对于客户端代码,与 ArrayList 相比,使用 List 时添加的唯一语法是声明和实例化中的类型参数。虽然这稍微增加了些编码的复杂性,但好处是您可以创建一个比 ArrayList 更安全并且速度更快的列表,特别适用于列表项是值类型的情况。 泛型类型参数(C# 编程指南)在泛型类型或方法定义中,类型参数是客户端在实例化泛型类型的变量时指定的特定类型的占位符。泛型类(如泛型介绍(C# 编程指南)中列出的 GenericList)不可以像这样使用,因为它实际上并不是一个类型,而更像是一个类型的蓝图。若要使用 GenericList,客户端代码必须通过指定尖括号中的类型参数来声明和实例化构造类型。此特定类的类型参数可以是编译器识别的任何类型。可以创建任意数目的构造类型实例,每个实例使用不同的类型参数,如下所示:C#复制代码GenericList list1 = new GenericList();GenericList list2 = new GenericList();GenericList list3 = new GenericList();在每个 GenericList 实例中,类中出现的每个 T 都会在运行时替换为相应的类型参数。通过这种替换方式,我们使用一个类定义创建了三个独立的类型安全的有效对象。有关 CLR 如何执行此替换的更多信息,请参见运行库中的泛型(C# 编程指南)。类型参数命名准则 务必使用描述性名称命名泛型类型参数,除非单个字母名称完全可以让人了解它表示的含义,而描述性名称不会有更多的意义。C#复制代码public interface ISessionChannel /*.*/ public delegate TOutput Converter(TInput from);public class List /*.*/ 考虑使用 T 作为具有单个字母类型参数的类型的类型参数名。C#复制代码public int IComparer() return 0; public delegate bool Predicate(T item);public struct Nullable where T : struct /*.*/ 务必将“T”作为描述性类型参数名的前缀。C#复制代码public interface ISessionChannel TSession Session get; 考虑在参数名中指示对此类型参数的约束。例如,可以将带有 ISession 约束的参数命名为 TSession。类型参数的约束(C# 编程指南)在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。下表列出了六种类型的约束:约束 说明 T:结构类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)。T:类类型参数必须是引用类型,包括任何类、接口、委托或数组类型。T:new()类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。T:类型参数必须是指定的基类或派生自指定的基类。T:类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。T:U为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。使用约束的原因 如果要检查泛型列表中的某个项以确定它是否有效,或者将它与其他某个项进行比较,则编译器必须在一定程度上保证它需要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。这种保证是通过对泛型类定义应用一个或多个约束获得的。例如,基类约束告诉编译器:仅此类型的对象或从此类型派生的对象才可用作类型参数。一旦编译器有了这个保证,它就能够允许在泛型类中调用该类型的方法。约束是使用上下文关键字 where 应用的。下面的代码示例演示可通过应用基类约束添加到 GenericList 类(在泛型介绍(C# 编程指南)中)的功能。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 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 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 继承的对象。 可以对同一类型参数应用多个约束,并且约束自身可以是泛型类型,如下所示:C#复制代码class EmployeeList where T : Employee, IEmployee, System.IComparable, new() / .通过约束类型参数,可以增加约束类型及其继承层次结构中的所有类型所支持的允许操作和方法调用的数量。因此,在设计泛型类或方法时,如果要对泛型成员执行除简单赋值之外的任何操作或调用 System.Object 不支持的任何方法,您将需要对该类型参数应用约束。在应用 where T : class 约束时,建议不要对类型参数使用 = 和 != 运算符,因为这些运算符仅测试引用同一性而不测试值相等性。即使在用作参数的类型中重载这些运算符也是如此。下面的代码说明了这一点;即使 String 类重载 = 运算符,输出也为 false。C#复制代码public static void OpTest(T s, T t) where T : class System.Console.WriteLine(s = t);static void Main() string s1 = foo; System.Text.StringBuilder sb = new System.Text.StringBuilder(foo); string s2 = sb.ToString(); OpTest(s1, s2);这种情况的原因在于,编译器在编译时仅知道 T 是引用类型,因此必须使用对所有引用类型都有效的默认运算符。如果需要测试值相等性,建议的方法是同时应用 where T : IComparable 约束,并在将用于构造泛型类的任何类中实现该接口。未绑定的类型参数 没有约束的类型参数(如公共类 SampleClass 中的 T)称为未绑定的类型参数。未绑定的类型参数具有以下规则: 不能使用 != 和 = 运算符,因为无法保证具体类型参数能支持这些运算符。 可以在它们与 System.Object 之间来回转换,或将它们显式转换为任何接口类型。 可以将它们与 null 进行比较。将未绑定的参数与 null 进行比较时,如果类型参数为值类型,则该比较将始终返回 false。裸类型约束 用作约束的泛型类型参数称为裸类型约束。当具有自己的类型参数的成员函数需要将该参数约束为包含类型的类型参数时,裸类型约束很有用,如下面的示例所示:C#复制代码class List void Add(List items) where U : T /*.*/在上面的示例中,T 在 Add 方法的上下文中是一个裸类型约束,而在 List 类的上下文中是一个未绑定的类型参数。裸类型约束还可以在泛型类定义中使用。注意,还必须已经和其他任何类型参数一起在尖括号中声明了裸类型约束: C#复制代码/naked type constraintpublic class SampleClass where T : V 泛型类的裸类型约束的作用非常有限,因为编译器除了假设某个裸类型约束派生自 System.Object 以外,不会做其他任何假设。在希望强制两个类型参数之间的继承关系的情况下,可对泛型类使用裸类型约束。 泛型类(C# 编程指南)泛型类封装不是特定于具体数据类型的操作。泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等,其中,像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。对于大多数需要集合类的方案,推荐的方法是使用 .NET Framework 2.0 类库中所提供的类。有关使用这些类的更多信息,请参见 .NET Framework 类库中的泛型(C# 编程指南)。 一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时,需要特别注意以下事项: 将哪些类型通用化为类型参数。一般规则是,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。但是,太多的通用化会使其他开发人员难以阅读或理解代码。 如果存在约束,应对类型参数应用什么约束(请参见类型参数的约束(C# 编程指南)。一个有用的规则是,应用尽可能最多的约束,但仍使您能够处理需要处理的类型。例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。这可以防止您的类被意外地用于值类型,并允许您对 T 使用 as 运算符以及检查空值。 是否将泛型行为分解为基类和子类。由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。有关从泛型基类继承的规则,请参见下面的内容。 是否实现一个或多个泛型接口。 例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能需要实现一个接口,如 IComparable,其中 T 是您的类的类型。有关简单泛型类的示例,请参见泛型介绍(C# 编程指南)类型参数和约束的规则对于泛型类行为有几方面的含义,特别是关于继承和成员可访问性。请务必先理解一些术语,然后再继续进行。对于泛型类 Node,,客户端代码可以通过指定类型参数引用该类,以创建封闭式构造类型 (Node),或者可以让类型参数处于未指定状态(例如在指定泛型基类时)以创建开放式构造类型 (Node)。泛型类可以从具体的、封闭式构造或开放式构造基类继承:C#复制代码class BaseNode class BaseNodeGeneric / concrete typeclass NodeConcrete : BaseNode /closed constructed typeclass NodeClosed : BaseNodeGeneric /open constructed type class NodeOpen : BaseNodeGeneric 非泛型(具体)类可以从封闭式构造基类继承,但无法从开放式构造类或裸类型参数继承,因为在运行时客户端代码无法提供实例化基类所需的类型变量。C#复制代码/No errorclass Node1 : BaseNodeGeneric /Generates an error/class Node2 : BaseNodeGeneric /Generates an error/class Node3 : T 从开放式构造类型继承的泛型类必须为任何未被继承类共享的基类类型参数提供类型变量,如以下代码所示:C#复制代码class BaseNodeMultiple /No errorclass Node4 : BaseNodeMultiple /No errorclass Node5 : BaseNodeMultiple /Generates an error/class Node6 : BaseNodeMultiple 从开放式构造类型继承的泛型类必须指定约束,这些约束是基类型约束的超集或暗示基类型约束:C#复制代码class NodeItem where T : System.IComparable, new() class SpecialNodeItem : NodeItem where T : System.IComparable, new() 泛型类型可以使用多个类型参数和约束,如下所示:C#复制代码class SuperKeyType where U : System.IComparable where V : new() 开放式构造类型和封闭式构造类型可以用作方法参数:C#复制代码void Swap(List list1, List list2) /code to swap itemsvoid Swap(List list1, List list2) /code to swap items泛型类是不变的。也就是说,如果输入参数指定 List,则当您试图提供 List 时,将会发生编译时错误。 泛型方法(C# 编程指南)泛型方法是使用类型参数声明的方法,如下所示:C#复制代码static void Swap(ref T lhs, ref T rhs) T temp; temp = lhs; lhs = rhs; rhs = temp;下面的代码示例演示一种使用 int 作为类型参数的方法调用方式:C#复制代码public static void TestSwap() int a = 1; int b = 2; Swap(ref a, ref b); System.Console.WriteLine(a + + b);也可以省略类型参数,编译器将推断出该参数。下面对 Swap 的调用等效于前面的调用:C#复制代码Swap(ref a, ref b);相同的类型推断规则也适用于静态方法以及实例方法。编译器能够根据传入的方法参数推断类型参数;它无法仅从约束或返回值推断类型参数。因此,类型推断不适用于没有参数的方法。类型推断在编译时、编译器尝试解析任何重载方法签名之前进行。编译器向共享相同名称的所有泛型方法应用类型推断逻辑。在重载解析步骤中,编译器仅包括类型推断取得成功的那些泛型方法。在泛型类中,非泛型方法可以访问类级别类型参数,如下所示:C#复制代码class SampleClass void Swap(ref T lhs, ref T rhs) 如果定义的泛型方法接受与包含类相同的类型参数,编译器将生成警告 CS0693,因为在方法范围内,为内部 T 提供的参数将隐藏为外部 T 提供的参数。除了类初始化时提供的类型参数之外,如果需要灵活调用具有类型参数的泛型类方法,请考虑为方法的类型参数提供其他标识符,如下面示例中的 GenericList2 所示。C#复制代码class GenericList / CS0693 void SampleMethod() class GenericList2 /No warning void SampleMethod() 使用约束对方法中的类型参数启用更专门的操作。此版本的 Swap 现在称为 SwapIfGreater,它只能与实现 IComparable 的类型参数一起使用。C#复制代码void SwapIfGreater(ref T lhs, ref T rhs) where T : System.IComparable T temp; if (lhs.CompareTo(rhs) 0) temp = lhs; lhs = rhs; rhs = temp; 泛型方法可以使用许多类型参数进行重载。例如,下列方法可以全部存在于同一个类中:C#复制代码void DoWork() void DoWork() void DoWork() 运行库中的泛型(C# 编程指南)将泛型类型或方法编译为 Microsoft 中间语言 (MSIL) 时,它包含将其标识为具有类型参数的元数据。泛型类型的 MSIL 的使用因所提供的类型参数是值类型还是引用类型而不同。第一次用值类型作为参数来构造泛型类型时,运行库会创建专用泛型类型,将提供的参数代入到 MSIL 中的适当位置。对于每个用作参数的唯一值类型,都会创建一次专用泛型类型。例如,假设您的程序代码声明了一个由整数构造的堆栈,如下所示:C#复制代码Stack stack;在此位置,运行库生成 Stack 类的专用版本,并

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论