泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,通过使用泛型类型参数 T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险
在泛型类型或泛型方法的定义中,类型参数是一个占位符(placeholder),通常为一个大写字母,如T。在客户代码声明、实例化该类型的变量时,把 T替换为客户代码所指定的数据类型。 说到泛型就不得不说说object,list,var,dynamic,这三个是依次出现的,都是时代进步的产物。
object类型
> 优点:
> 1. object类型可以用来引用任何类型的实例;
> 2. object类型可以存储任何类型的值;
> 3. 可以定义object类型的参数;
> 4. 可以把object作为返回类型。
> 缺点:
> 1. 会因为程序员没有记住使用的类型而出错,造成类型不兼容;
> 2. 值类型和引用类型的互化即装箱拆箱使系统性能下降。
list类型
> 1. 性能高:定义数据类型,不需要类型转换,避免拆装箱带来的性能损失;
> 2. 类型安全:定义允许使用的数据类型,在编译时检查类型错误,及早发现错误。
> 3. 泛型是程序编译的时候,他会把它编译成所需要的类型,主要是减少了代码量,方便程序员。
var类型
var实际上是编译期抛给我们的“语法糖”,一旦被编译,编译期会自动匹配var 变量的实际类型,并用实际类型来替换该变量的申明,这看上去就好像我们在编码的时候是用实际类型进行申明的,vs中鼠标放上取得时候会显示类型。
dynamic类型
dynamic是FrameWork4.0的新特性。dynamic的出现让C#具有了弱语言类型的特性。编译器在编译的时候不再对类型进行检查,编译期默认dynamic对象支持你想要的任何特性。
而dynamic被编译后,实际是一个object类型,只不过编译器会对dynamic类型进行特殊处理,让它在编译期间不进行任何的类型检查,而是将类型检查放到了运行期。vs中可以看出来,object的点上去还是object,var点上去是需要的类型,dynamic点上去dynamic。
object 下面的步骤会保存,obj编译的时候就不会通过,而dynamic则会绕过编译器,运行的时候去解析,运行的时候dynamic是不存在的。var下面也是可以通过的,鼠标点上去会是int型。
``` C# var testInt = 1; testInt = testInt + 1; ```
泛型类型详解
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 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(); }
其他的基础功能
5. C#中泛型的解释(object,list,var,dynamic的区别)
6. C#中委托
7. C#和.NET版本对比