泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,通过使用泛型类型参数 T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险
泛型概述:
1.使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
2.泛型最常见的用途是创建集合类。
3..NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类,如 System.Collections 命名空间中的 ArrayList。
4.您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
5.可以对泛型类进行约束以访问特定数据类型的方法。
6.关于泛型数据类型中使用的类型的信息可在运行时通过反射获取。
泛型好处举例:
1.ArrayList 是一个使用起来非常方便的集合类,无需进行修改即可用来存储任何引用或值类型。但这种方便是需要付出代价的。添加到 ArrayList 中的任何引用或值类型都将隐式地向上强制转换为 Object。如果项是值类型,则必须在将其添加到列表中时进行装箱操作,在检索时进行取消装箱操作。强制转换以及装箱和取消装箱操作都会降低性能;在必须对大型集合进行循环访问的情况下,装箱和取消装箱的影响非常明显。
2.另一个限制是缺少编译时类型检查;因为 ArrayList 将把所有项都强制转换为 Object,所以在编译时无法防止客户端代码执行
3.这种做法更可能产生编程错误,并且直到运行时才能检测到此错误
4.对于客户端代码,与 ArrayList 相比,使用 List<T> 时添加的唯一语法是声明和实例化中的类型参数。虽然这稍微增加了些编码的复杂性,但好处是您可以创建一个比 ArrayList更安全并且速度更快的列表,特别适用于列表项是值类型的情况。
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。下表列出了六种类型的约束:
约束 | 说明 |
---|---|
T:结构 |
类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)。 |
T:类 |
类型参数必须是引用类型,包括任何类、接口、委托或数组类型。 |
T:new() |
类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。 |
T:<基类名> |
类型参数必须是指定的基类或派生自指定的基类。 |
T:<接口名称> |
类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 |
T:U |
为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。 |
举例:
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new() { // ... }
class SuperKeyType<K, V, U> where U : System.IComparable<U> where V : new() { }
泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等,其中,像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。
一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时,需要特别注意以下事项:
-
将哪些类型通用化为类型参数。
一般规则是,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。但是,太多的通用化会使其他开发人员难以阅读或理解代码。
-
如果存在约束,应对类型参数应用什么约束
一个有用的规则是,应用尽可能最多的约束,但仍使您能够处理需要处理的类型。例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。这可以防止您的类被意外地用于值类型,并允许您对 T 使用 as 运算符以及检查空值。
-
是否将泛型行为分解为基类和子类。
由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。有关从泛型基类继承的规则,请参见下面的内容。
-
是否实现一个或多个泛型接口。
例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能需要实现一个接口,如 IComparable<T>,其中 T 是您的类的类型。
为泛型集合类或表示集合中项的泛型类定义接口通常很有用。对于泛型类,使用泛型接口十分可取,例如使用 IComparable<T> 而不使用 IComparable,这样可以避免值类型的装箱和取消装箱操作。.NET Framework 2.0 类库定义了若干新的泛型接口,以用于 System.Collections.Generic 命名空间中新的集合类。
举例:
//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"); } }
泛型方法是使用类型参数声明的方法,如下所示:
static void Swap<T>(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; }
根据典型设计模式定义事件时,泛型委托尤其有用,因为发送方参数可以为强类型,不再需要强制转换成 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; }
下面是链表
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; /** * 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 * 链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。 * 每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 * 相比于线性表顺序结构,操作复杂。 * **/ namespace NETTest { public class NodeTable { } /// <summary> /// 这个类仅包含一个表示员工名字的字符串类型,一个设置员工名字的构造函数,一个返回Employee名字的ToString()方法。 /// </summary> public class Employee { private string name; public Employee(string name) { this.name = name; } public override string ToString() { return this.name; } } public class Node<T> { Object data; Node<T> next; //注意构造函数将私有的数据成员设置成传递进来的对象,并且将 next 字段设置成null。 public Node(Object data) { this.data = data; this.next = null; } public Object Data { get { return this.data; } set { data = value; } } public Node<T> Next { get { return this.next; } set { this.next = value; } } /// <summary> /// 首先检测当前Node的next字段,看它是不是null。 /// 如果是,那么当前Node就是最后一个Node,我们将当前Node的next属性指向传递进来的新结点, /// 这样,我们就把新Node插入到了链表的尾部。 /// 如果当前Node的next字段不是null,说明当前node不是链表中的最后一个node。 /// 因为next字段的类型也是node,所以我们调用next字段的Append方法(注:递归调用),再一次传递Node参数,这样继续下去,直到找到最后一个Node为止。 /// </summary> /// <param name="newNode"></param> public void Append(Node<T> newNode) { if (this.next == null) { this.next = newNode; } else { next.Append(newNode); } } /// <summary> /// Node 类中的 ToString() 方法也被覆盖了,用于输出 data 中的值,并且调用下一个 Node 的 ToString()方法(译注:再一次递归调用)。 /// </summary> /// <returns></returns> public override string ToString() { string output = data.ToString(); if (next != null) { output += ", " + next.ToString(); } return output; } } /** * LinkedList 类本身只包含对一个Node的引用,这个Node称作 HeadNode,是链表中的第一个Node,初始化为null。 * **/ public class LinkedList<T> { Node<T> headNode = null; /// <summary> /// LinkedList 类不需要构造函数(使用编译器创建的默认构造函数), /// 但是我们需要创建一个公共方法,Add(),这个方法把 data存储到线性链表中。 /// 这个方法首先检查headNode是不是null, /// 如果是,它将使用data创建结点,并将这个结点作为headNode, /// 如果不是null,它将创建一个新的包含data的结点,并调用headNode的Append方法 /// </summary> /// <param name="data"></param> public void Add(Object data) { if (headNode == null) { headNode = new Node<T>(data); } else { headNode.Append(new Node<T>(data)); } } /// <summary> /// 线性链表创建一个索引器。 /// </summary> /// <param name="index"></param> /// <returns></returns> public object this[int index] { get { int ctr = 0; Node<T> node = headNode; while (node != null && ctr <= index) { if (ctr == index) { return node.Data; } else { node = node.Next; } ctr++; } return null; } } /// <summary> /// 用以调用headNode的ToString()方法。 /// </summary> /// <returns></returns> public override string ToString() { if (this.headNode != null) { return this.headNode.ToString(); } else { return string.Empty; } } } }
static void Main(string[] args) { /**泛型**/ LinkedList<int> ll = new LinkedList<int>(); for (int i = 0; i < 10; i++) { ll.Add(i); } Console.Write(ll); Console.WriteLine(" Done."); LinkedList<Employee> employees = new LinkedList<Employee>(); employees.Add(new Employee("Jason1")); employees.Add(new Employee("Jason2")); employees.Add(new Employee("Jason3")); employees.Add(new Employee("Jason4")); Console.Write(employees); Console.WriteLine(" Done."); Console.WriteLine("The fourth integer is {0}.", ll[3]); Employee d = (Employee)employees[1]; Console.WriteLine("The second Employee is {0}.", d); Console.ReadLine(); }