C#泛型
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
泛型是 2.0 版 C#语言和公共语言运行库(CLR)中的一个新功能,它将类型参数的概 念引入.NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个 或多个类型的指定推迟到客户端代码声明并实例化该类或方法时. 例如, 通过使用泛型类型 参数 T,可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操 作的成本或风险, 避免进行强制类型转换的需求提高类型安全性. 这样开发人员可以更轻松 地创建泛化的类和方法.本节将对泛型及其应用进行详细讲解. 5.10.1 泛型概述及优点 泛型类和泛型方法同时具备可重用性, 类型安全和效率高等特点, 它通常用在集合和在 集合上运行的方法中 2.0 版类库提供一个新的名为 System.Collections.Generic 的命名 空间,其中包含几个新的基于泛型的集合类.建议面向 2.0 版的所有应用程序都尝试使用新 的泛型集合类. 同样, 也可以创建自定义泛型类型和方法, 以提供个人的通用解决方案和设计类型安全 的高效模式等. 泛型类和方法接受"类型参数",它们指定了要操作的对象的类型.例如:
public class Test<T> { }
在实例化时才指定类型.例如:
Test<int> tree = new Test<int>();
使用泛型类型有以下优点: 使用泛型类型可以最大限度地重用代码,保护类型的安全以及提高性能; 使用泛型可以创建集合类; . NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类, 应尽可能地使用这些类来代替普通的类,如 System.Collections 命名空间中的 ArrayList; 可以创建自己的泛型接口,泛型类,泛型方法,泛型事件和泛型委托; 可以对泛型类进行约束以访问特定数据类型的方法; 关于泛型数据类型中使用的类型的信息可在运行时通过反射获取.
在公共语言运行时和 C# 语言的早期版本中,通用化是通过在类型与通用基类型 Object 之间进行强制 转换来实现的,泛型提供了针对这种限制的解决方案.通过创建泛型类,您可以创建一个在编译时类型安 全的集合.
使用非泛型集合类的限制可以通过编写一小段程序来演示,该程序利用 .NET Framework 类库中的 ArrayList 集合类.ArrayList 是一个使用起来非常方便的集合类,无需进行修改即可用来存储任何引用 或值类型.
// The .NET Framework 1.1 way to create a list: 1 2 3 4 5 6 7 8 System.Collections.ArrayList list2 = new System.Collections.ArrayList(); list2.Add("It is raining in Redmond."); list2.Add("It is snowing in the mountains."); System.Collections.ArrayList list1 = new System.Collections.ArrayList(); list1.Add(3); list1.Add(105);
但这种方便是需要付出代价的.添加到 ArrayList 中的任何引用或值类型都将隐式地向上强制转换为 Object.如果项是值类型,则必须在将其添加到列表中时进行装箱操作,在检索时进行取消装箱操作.强 制转换以及装箱和取消装箱操作都会降低性能;在必须对大型集合进行循环访问的情况下,装箱和取消装 箱的影响非常明显.
另一个限制是缺少编译时类型检查;因为 ArrayList 会将所有项都强制转换为 Object,所以在编译时无 法防止客户端代码执行类似如下的操作:
1 2 3 4 5 6 7
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.");
8 9 10 11 12 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<(Of <(T>)>) 集合中,向集合添加项的操作类似于以下形式:
1 2 3 4 5 6 7 8
// 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<(Of <(T>)>) 时添加的唯一语法是声明和实例化中的 类型参数.虽然这种方式稍微增加了编码的复杂性,但好处是您可以创建一个比 ArrayList 更安全并且速 度更快的列表,对于列表项是值类型的情况尤为如此.
泛型是对 CLR 类型系统的扩展, 它允许开发人员定义那些未指定某些细节的类型. 相 反,当用户代码引用该代码时,就会指定这些细节.引用泛型的代码填充缺少的细节,并根 据其特定需求对类型进行调整. 泛型的命名反映了该功能的目标: 允许在编写代码时不指定 可能限制其使用范围的细节.代码本身就是泛型.稍后,我会对它进行更详细的介绍. CLR 泛型预览 正如使用任何新技术一样,明白它的好处所在会有所帮助.那些熟悉 C++ 模板的用户 将会发现,泛型在托管代码中具有相似的用途.但是,我不愿意对 CLR 泛型和 C++ 模板 进行过多比较,因为泛型具有一些额外的好处,它不存在以下两个常见问题:代码臃肿和开 发人员混淆. CLR 泛型具有一些好处,如编译时类型安全,二进制代码重用,性能和清晰性.我将 简要介绍这些好处,您在阅读本专栏的其余文章时,会更详细地了解它们.例如,假设有两 个集合类:SortedList(Object 引用的集合)和 GenericSortedList< T>(任意类型的集合). 类型安全:当用户向 SortedList 类型的集合内添加 String 时,String 会隐式强制转换 为 Object.同样,如果从该列表中检索 String 对象,则它必须在运行时从 Object 引用强 制转换到 String 引用.这会造成编译时缺少类型安全,从而使开发人员感到厌烦,并且易 于出错.相反,如果使用 GenericSortedList< String>(T 的类型被设置为 String),就会使 所有的添加和查找方法使用 String 引用.这允许在编译时(而非运行时)指定和检查元素 的类型. 二进制代码重用:为了进行维护,开发人员可以选择使用 SortedList ,通过从它派生 SortedListOfString 来实现编译时的类型安全.此方法有一个问题,那就是必须对于每个需 要类型安全列表的类型都编写新代码,而这会很快变成非常费力的工作.使用 GenericSortedList< T>,需要执行的全部操作就是将具有所需元素类型的类型实例化为 T. 泛型代码还有一个附加价值,那就是它在运行时生成,因此,对于无关元素类型的两个扩展 (如 GenericSortedList< String> 和 GenericSortedList< FileStream>)能够重新使用同一个实 时 (JIT) 编译代码的大部分.CLR 只是处理细节 — 代码不再臃肿! 性能: 关键在于: 如果类型检查在编译时间进行, 而不是在运行时间进行, 则性能增强. 在托管代码中, 引用和值之间的强制转换既会导致装箱又会导致取消装箱, 而且避免这样的 强制转换可能会对性能产生同样的负面影响. 最近针对一个由一百万个整数组成的数组进行 了快速排序法基准测试, 结果表明泛型方法比非泛型方法快三倍. 这是由于完全避免了对这 些值进行装箱. 如果针对由字符串引用组成的数组进行同样的排序, 则由于无需在运行时执 行类型检查,因此使用泛型方法后性能提高了 20%.
清晰性:泛型的清晰性体现在许多方面.约束是泛型的一个功能,它们会禁止对泛型代 码进行不兼容的扩展;使用泛型,您将不再面临那些困扰 C++ 模板用户的含混不清的编译 器错误.在 GenericSortedList< T> 示例中,集合类将有一个约束,该约束使集合类只处理 可进行比较并依此进行排序的 T 类型.同样,通常可以使用名为类型推理的功能来调用泛 型方法,而无需使用任何特殊语法.当然,编译时类型安全可以使应用程序代码更加清晰. 我将在本文中详细介绍约束,类型推理和类型安全
一个简单的 CLR 泛型示例 Whidbey CLR 版本将通过类库中的一套泛型集合类来提供这些现成的好处.但是,可 通过为应用程序定义其自己的泛型代码, 使其进一步受益于泛型. 为了解释这是如何完成的, 我将首先修改一个简单的链接列表节点类,使其成为泛型类类型. 以下代码中的 Node 类只是包括一些基本内容.它有两个字段:m_data(引用节点的 数据)和 m_data(引用链接列表中的下一项).这两个字段都是由构造函数方法设置的. 确实只有两个其他点缀性功能,第一个功能是通过名为 Data 和 Next 的只读属性访问 m_data 和 m_next 字段.第二个功能是对 System.Object 的 ToString 虚拟方法进行重写.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. } // Access the next node public Node Next { get { return m_next; } } // Access the data for the node public Object Data { get { return m_data; } } public Node(Object data, Node next) { m_data = data; m_next = next; // Definition of a node type for creating a linked list class Node { Object Node m_data; m_next; using System;
22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. } } // Output sum Console.WriteLine("Sum of nodes = {0}", sum); } // Sum-up integers by traversing linked list Int32 sum = 0; for (Node current = head; current != null; current = current.Next) { sum += (Int32) current.Data; // Create a linked list of integers Node head = new Node(5, null); head = new Node(10, head); head = new Node(15, head); // Code that uses the node type class App { public static void Main() { } } // Get a string representation of the node public override String ToString() { return m_data.ToString();
上面还显示了使用 Node 类的代码.该引用代码会受到某些限制.问题在于,为了能 在许多上下文中使用,其数据必须为最基本的类型,即 System.Object.这意味着使用 Node 时,就会失去任何形式的编译时类型安全.使用 Object 意味着算法或数据结构中的"任意 类型"会强迫所使用的代码在 Object 引用和实际数据类型之间进行强制转换.应用程序中 的任何类型不匹配错误只有在运行之后才被捕获. 如果在运行时尝试进行强制转换, 这些错 误会采用 InvalidCastException 形式. 此外,如果要向 Object 引用赋予任何基元值(如 Int32),则需要对实例进行装箱. 装箱涉及到内存分配和内存复制, 以及最后对已装箱值进行的垃圾回收. 最后, 正如可在 图 1 中看到的那样,从 Object 引用强制转换为值类型(如 Int32)会导致取消装箱(也包括
类型检查) 由于装箱和取消装箱会损害该算法的整体性能, . 因此您会明白为什么用 Object 就意味着"任何类型"都具有一定的缺点. 使用泛型重写 Node 是解决这些问题的完美方法.让我们看一下下面的代码,您将发 现 Node 类型被重写为 Node< T> 类型.具有泛型行为的类型(如 Node< T>)是参数化类 型, 并且可被称作 Parameterized Node, Node of T 或泛型 Node. 稍后我将介绍这个新的 C# 语法;让我们首先深入研究一下 Node< T> 与 Node 有何不同.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. } } // Get a string representation of the node public override String ToString() { return m_data.ToString(); } // Access the next node public Node Next { get { return m_next; } set { m_next = value; } } // Access the data for the node public T Data { get { return m_data; } set { m_data = value; } } public Node(T data, Node next) { m_data = data; m_next = next; class Node { T Node m_data; m_next;
Node< T> 类型与 Node 类型在功能和结构上相似. 二者均支持为任何给定类型的数据 构建链接列表.但是,Node 使用 System.Object 来表示"任意类型",而 Node< T> 不指定 该类型.相反,Node< T> 使用名为 T 且作为类型占位符的类型参数.当使用者代码使用 Node< T> 时,名为 T 的类型参数最终由 Node< T> 的参数来指定.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17.
class App { public static void Main() {
// Create a linked list of integers Node head = new Node(5, null); head = new Node(10, head); head = new Node(15, head);
// Sum up integers by traversing linked list Int32 sum = 0; for (Node current = head; current != null; current = current.Next) { sum += current.Data; }
// Output sum Console.WriteLine("Sum of nodes = {0}", sum.ToString());
18. 19. }
}
以上中的代码使用了具有 32 位带符号整数的 Node< T>,这是通过构造类似类型名 称:Node< Int32> 来实现的.在本例中,Int32 是类型参数 T 的类型变量.(顺便说一句, C# 还将接受 Node< int>,以便将 T 指示为 Int32.) 如果该代码需要某种其他类型(如 String 引用)的链接列表,则这可通过将它指定为 T 的类型变量来完成,例如:Node< String>. Node< T> 的好处在于:它的算法行为可被明确定义,而它所操作的数据类型仍保持未 指定状态.因此,Node< T> 类型在工作方式上是具体的;而泛型在所处理的内容方面又是 具体的.总之,诸如链接列表应当拥有的数据类型等细节最好留给使用 Node< T> 的代码 来指定. 在讨论泛型时,最好先明确两种角色:定义代码和引用代码.定义代码包括既声明泛型 代码存在又定义类型成员(如方法和字段)的代码. 图 2 中显示的是类型 Node< T> 的 定义代码.引用代码是用户代码,它使用预定义的泛型代码,并且该代码还可以内置到另一 个程序集中. 图 3 是 Node< T> 的引用代码示例. 考虑定义代码和引用代码非常有用, 原因在于这两种角色都在实际的可使用泛型代码构 造中起着一定的作用. 图 3 中的引用代码使用 Node< T> 来构造一个名为 Node< T> 的
新类型. Node< Int32> 是一个截然不同的类型, 它由以下两个关键成分构建而成: Node< T> (由定义代码创建),参数 T 的类型变量 Int32(由引用代码指定).只有使用这两个成 分才能使泛型代码变得完整. 请注意,从面向对象的派生角度看,泛型类型(如 Node< T>)以及从泛型类型构造的 类型(如 Node< Int32> 或 Node< String>)并不是相关类型.类型 Node< Int32>,Node< String> 和 Node< T> 类型是同辈,它们都是从 System.Object 直接派生而来.
C# Generic 语法 CLR 支持多种编程语言,因此,CLR 泛型将有多种语法.但是,无论采用哪种语法, 用一种面向 CLR 的语言编写的泛型代码将可以由其他面向 CLR 的语言编写的程序使用. 我将在本文中介绍 C# 语法,其原因是,在编写本文时,在三种较大的托管语言中,泛型 的 C# 语法相当稳定. 但是, 没有必要在 Visual Basic?.NET 和 Managed C++ 的 Whidbey 版本中支持泛型. 下表显示了泛型定义代码和泛型引用代码的基本 C# 语法.二者的语法区别反映了泛 型代码所涉及的双方的不同职责.
Defining Code class Node<T> { T Node<T> } struct Pair<T,U> { T U } interface IComparable<T> { Int32 CompareTo(T other); } } void Swap(ref T item1, ref T item2) { Decimal d1 = 0, d2 = 2; T temp = item1; item1 = item2; item2 = temp; } delegate void EnumerateItem(T item); ... EnumerateItem<Int32> callback = new EnumerateItem<Int32>(CallMe); } void CallMe(Int32 num) { ... } Swap<Decimal>(ref d1, ref d2); class MyType : IComparable<MyType> { public Int32 CompareTo(MyType other) { ... } m_element1; m_element2; Pair<Byte,String> pair; pair.m_element1 = 255; pair.m_element2 = "Hi"; m_data; m_next; Referencing Code class Node8Bit : Node<Byte> { ...}
目前的计划是让 CLR(从而让 C#)支持泛型类,结构,方法,接口和委托. 上表的 左侧显示了每种定义代码情况的 C# 语法示例..请注意,尖括号表示类型参数列表.尖括 号紧跟在泛型类型或成员的名称后面.同样,在类型参数列表中有一个或多个类型参数.参 数还出现在泛型代码的整个定义中,用来替代特定的 CLR 类型或作为类型构造函数的参 数. 图 4 的右侧显示了与之相匹配的引用代码情况的 C# 语法示例.请注意,在此处, 类型变量括在尖括号中;泛型标识符和括号构成一个截然不同的新标识符.另外还要注意, 类型变量指定在从泛型构造类型或方法时所使用的类型. 让我们花一点时间来定义代码语法.当编译器遇到一个由尖括号分开的类型参数列表 时, 它可识别出您在定义泛型类型或方法. 泛型定义中的尖括号紧跟在所定义的类型或方法 的名称后面. 类型-参数列表指出要在泛型代码定义中保持未指定状态的一个或多个类型.类型参数 的名称可以是 C# 中任何有效的标识符,它们可用逗号隔开.对于"定义代码"部分中的类 型参数,需要注意下面一些事项: 在每个代码示例中,可以看到在整个定义中(通常将出现类型名称的位置)均使用了类 型参数 T 或 U. 在 IComparable< T> 接口示例中,可以看到同时使用类型参数 T 和常规类型 Int32. 在泛型代码的定义中, 既可以使用未指定的类型 (通过类型参数) 又可以使用指定的类型 (使 用 CLR 类型名称). 在 Node< T> 示例中,可以看到,类型参数 T 可以像在 m_data 的定义中一样独立使 用, 还可以像在 m_next 中一样用作另一个类型构造的一部分. 用作另一个泛型类型定义的 变量的类型参数 (如 Node< T>) 称作开放式泛型类型. , 用作类型参数的具体类型 (如 Node< System.Byte>),称作封闭式泛型类型. 与任何泛型方法一样, 表格中显示的示例泛型方法 Swap< T> 可以是泛型或非泛型类 型的一部分,也可以是实例,虚拟或静态方法. 在本文中,我对于类型参数使用的是单字符名称(如 T 和 U),这主要是为了使情况 更简单.但是,您会发现也可以使用描述性名称.例如,在产品代码中,Node< T> 类型可 被等效地定义为 Node< ItemType> 或 Node< dataType>. 在撰写本文时,Microsoft 已经使库代码中的单字符类型参数名称标准化,以有助于区 分这些名称与用于普通类型的名称.我个人比较喜欢在产品代码中使用 camelCasing 类型 参数,因为这可将它们与代码中的简单类型名称相区分,而同时又具有一定的描述性. 在泛型引用代码中, 未指定的类型会变成指定的类型. 如果引用代码实际使用泛型代码, 则这是十分必要的.如果您查看 图 4 中"Referencing Code"部分中的示例,就会发现在所
有情况中,新类型或方法都是通过将CLR 类型指定为泛型的类型变量,从一个泛型构造的。
在泛型语法中,诸如Node< Byte> 和Pair< Byte,String> 之类的代码表示从泛型类型定义构造的新类型的类型名称。
在深入介绍该技术本身之前,我将再介绍一个语法细节。
当代码调用泛型方法(如Swap< T> 方法)时,完全限定的调用语法包括任何类型变量。
但是,有时可以选择将类型变量从调用语法中排除,如下面的两行代码所示:
1.Decimal d1 = 0, d2 = 2;
2.Swap(ref d1, ref d2);
这个简化的调用语法依赖一个名为类型推理的C# 编译器功能,在该功能中,编译器使用传递给方法的参数类型来推导类型变量。
在本例中,编译器从d1 和d2 的数据类型来推导,类型参数T 的类型变量应当为System.Decimal。
如果存在多义性,类型推理对于调用方不工作,并且C# 编译器将会产生一个错误,建议您使用包含尖括号和类型变量的完整调用语法
CLR泛型:间接
我的一个好朋友喜欢指出,大多数完美的编程解决方案都是围绕添加另一间接层次而设计的。
指针和引用允许单个函数影响一个数据结构的多个实例。
虚拟函数允许单个调用站点将调用传送到一组相似的方法—其中一些方法可在以后定义。
这两个间接示例非常常见,以至于程序员通常注意不到间接本身。
间接的主要目的是为了提高代码的灵活性。
泛型是一种间接形式,在这种形式中,定义不会产生可直接使用的代码。
相反,在定义泛型代码中,会创建一个“代码工厂”。
随后,引用代码使用该工厂代码来构造可直接使用的代码。
让我们首先从泛型方法来了解这个设计思路。
图 5 中的代码定义并引用了一个名为CompareHashCodes< T> 的泛型方法。
定义代码创建了一个名为CompareHashCodes< T> 的泛型方法,但是图 5 中显示的代码都没有直接调用CompareHashCodes< T>。
相反,在Main 中,引用代码使用CompareHashCodes< T> 来构造两种不同的方法:CompareHashCodes< Int32> 和CompareHashCodes< String>。
这些构造方法是CompareHashCodes< T> 的实例,它们是由引用代码来调用的。
通常会在某个方法的定义中直接定义该方法所执行的操作。
与之相反,在泛型方法的定义中,会定义它的构造方法实例将执行的操作。
除了充当如何构造特定实例的模型以外,泛型方法本身不执行任何操作。
CompareHashCodes< T> 是一种泛型方法,通过它可以构造对哈希代码进行比较的方法实例。
构造实例(如CompareHashCodes< Int32>)执行实际工作;
它对整数的哈希代码进行比较。
相反,CompareHashCodes< T> 是一个从可调用中删除的间接层。
泛型类型类似于从与其相对应的简单副本中删除的一个间接层。
系统使用简单的类型定义(如类或结构)来创建内存中的对象。
例如,类库中的System.Collection.Stack 类型用于在内存中创建堆栈对象。
在某种意义上,可以将C# 中的新关键字或中间语言代码中的newobj 指令视为一个对象工厂,该对象工厂在创建对象实例时,将托管类型用作每个对象的蓝图。
另一方面,泛型类型用于实例化封闭式类型,而不是对象实例。
随后,可以使用从泛型类型构造的类型来创建对象。
让我们回顾一下在图 2 中定义的Node< T> 类型以及如图3所示的它的引用代码。
托管应用程序永远不能创建Node< T> 类型的对象,即使它是托管类型时也是如此。
这是由于Node< T> 缺乏足够的定义,因此无法被实例化为内存中的对象。
但是,在执行应用程序的过程中,Node< T> 可用于实例化另一个类型。
Node< T> 是一个开放式泛型类型,并且只用于创建其他构造类型。
如果使用Node< T> 创建的构造类型是封闭式类型(如Node< Int32>),则它可用于创建对象。
图 3 中的引用代码使用Node< Int32> 的方式与使用简单类型时大体相同。
它创建Node< Int32> 类型的对象,在这些对象上调用方法,等等。
泛型类型额外提供一个间接层,此功能非常强大。
采用泛型类型的引用代码会产生定制的托管类型。
在脑海中将泛型代码想象为从其简单副本中删除的一个间接层,这有助于凭直觉获知CLR 中泛型的许多行为、规则和用法。
CLR泛型小结
本文介绍了泛型类型的好处—如何使用它们改善类型安全、代码重用和性能。
本文还讲述了C# 中的语法以及泛型如何导致另一层间接,从而提高灵活性
为什么要使用C#泛型?
为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但功能是实现一个栈,这个栈只能处理int数据类型:
1.public class Stack
2. {
3. private int[] m_item;
4. public int Pop(){...}
5. public void Push(int item){...}
6. public Stack(int i)
7. {
8.this.m_item = new int[i];
9. }
10.}
上面代码运行的很好,但是,当我们需要一个栈来保存string类型时,该怎么办呢?很多人都会想到把上面的代码复制一份,把int改成string不就行了。
当然,这样做本身是没有任何问题的,但一个优秀的程序是不会这样做的,因为他想到若以后再需要long、Node 类型的栈该怎样做呢?还要再复制吗?优秀的程序员会想到用一个通用的数据类型object
来实现这个栈:
1.public class Stack
2. {
3. private object[] m_item;
4. public object Pop(){...}
5. public void Push(object item){...}
6. public Stack(int i)
7. {
8.this.m_item = new[i];
9. }
10. }
这个栈写的不错,他非常灵活,可以接收任何数据类型,可以说是一劳永逸。
但全面地讲,也不是没有缺陷的,主要表现在:
当Stack处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。
在处理引用类型时,虽然没有装箱和折箱操作,但将用到数据类型的强制转换操作,增加处理器的负担。