C#泛型总结
为什么要引进泛型
泛型类和泛型方法同时具备可重用性、类型安全和效率,这是非泛型类和非泛型方法无法具备的。
使用泛型的一个主要原因就是性能高。值类型存储在栈上,引用类型存储在堆上。C#类是引用类型,结构是值类型,值类型转换为引用类型称为装箱操作。把引用类型转换为值类型称为拆箱。装箱和拆箱操作很容易使用,但是性能损失比较大。当遍历的次数增加时,更明显。
类型安全:泛型和ArrayList类一样,如果适用对象,就可以在这个集合中添加任意类型,但是泛型类型T定义了允许使用的类型。在泛型类List<T>类中,List<int>的定义,就只能把整数类型添加到集合中。如果添加其他类型,编译器会报错。
二进制代码的重用性:泛型类可以只定义一次,并且可以用许多不同的类型实例化。
例如:使用List<T>类用一个int、一个string一个MyClass类型实例化。
代码扩展:在用不同的特定类型实例化泛型时,会创建多少代码呢。因为泛型类型会放在程序集中,所以用特定类型实例化泛型类不会在IL代码中复制这些类。但是在JIT编译器把泛型类编译为本地代码时,会给每个值类型创建一个新类。引用类型共享同一个本地类的多有相同的实现代码。这是因为引用类型在实例化的泛型类中只需要4个字节(32位操作系统)的内存地址,就可以引用一个引用类型。值类型包含在实例化的泛型类的内存中,同时因为每个值类型对内存的要求不同,所以要为每个类型实例化一个类。、
泛型约束
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。下表列出了六种类型的约束:
约束 | 说明 |
Where T:struct | 类型参数必须是值类型。 可以指定除 Nullable 以外的任何值类型。 |
Where T:class | 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。 |
Where T:IFoo | 类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。 |
Where T:Foo | 类型参数必须是指定的基类或派生自指定的基类。 |
Where T:new() | 类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。 |
Where T1:T2 | 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 |
如果要检查泛型列表中的某个项以确定它是否有效,或者将它与其他某个项进行比较,则编译器必须在一定程度上保证它需要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。这种保证是通过对泛型类定义应用一个或多个约束获得的。如果泛型类需要调用泛型类中的方法,必须添加约束。
程序:
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()
{
// ...
}
可以对多个参数应用约束:
class Base { }
class Test<T, U>
where U : struct
where T : Base,new() { }
默认值default
只有当实际类型给泛型的时候,才知道实际类型是引用类型还是值类型,所以要使用default关键字赋初值,当实际类型四引用类型的时候,赋值null,值类型的时候赋值0;
泛型类
泛型类封装不是特定于具体数据类型的操作。泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等。像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。
对于大多数需要集合类的方案,推荐的方法是使用 .NET Framework 类库中所提供的类。
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> { }
泛型接口
为泛型集合类或表示集合中项的泛型类定义接口通常很有用。 对于泛型类,使用泛型接口十分可取,例如使用 IComparable 而不使用 IComparable,这样可以避免值类型的装箱和取消装箱操作。
协变和抗变:这个我目前还不懂为什么,并且不知道为什么会出来。待我思考完。。。
泛型和数组
在 C# 2.0 以及更高版本中,下限为零的一维数组自动实现 IList。这使您可以创建能够使用相同代码循环访问数组和其他集合类型的泛型方法。此技术主要对读取集合中的数据很有用。 IList 接口不能用于在数组中添加或移除元素。如果尝试对此上下文中的数组调用 IList 方法(例如 RemoveAt),则将引发异常。
程序:
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(); }}
.NET Framework 类库中的泛型
了解更多的系统的泛型点击打开链接版权声明:个人财富,想学呀,我教你呀。。。。