[关闭]
@kuier1992 2015-10-28T15:02:25.000000Z 字数 4178 阅读 1274

泛型

C#


昨天写了委托,今天再把泛型给总结一下。还是按照书本的来,也当是为了将来面试而学习。

初识泛型

面向对象编程的好处之一是"代码重用",它极大地提高了开发效率。也就是说,可以派生出一个类,让它继承基类的所有能力。派生类只需要重写虚方法、或添加一些新方法,就可以定制派生类的行为,使之满足开发人员的要求。而泛型支持另一种形式的代码重用,即“算法重用”

CLR允许创建泛型引用类型泛型值类型,但不允许创建泛型枚举类型。此外,CLR还允许创建泛型接口和泛型委托
如在C#中广泛使用的List<T>,泛型List类的设计者紧接在类名后添加了一个<T>,表明它操作的是一个未指定的数据类型。T被称为类型参数,而其他开发人员为了使用泛型,要制定具体操作类型。使用泛型是指定的具体操作类型就被称为类型实参。如:开发人员可指定一个DataTime类型实参来使用List<DataTime>
泛型的优势总结:

源代码保护-使用泛型算法的开发人员不需要访问算法的源代码;
类型安全-指定类型参数时只有相符的类型实参才可以应用泛型算法;
更清晰的代码-减少了强制类型转换
更加的性能-不需要频繁的装箱拆箱

FCL中的泛型

泛型最明显的应用应该就是集合类。FCL在System.Collections.GenericSystem.Collections.ObjectModel命名空间提供了多个泛型集合类,System.Collections.Concurrent命名空间则提供了线程安全的泛型集合类。Microsoft建议使用泛型集合类
集合类实现了许多接口,所以放入集合中的对象可实现接口来执行搜索排序等操作。
简单使用

  1. //Array中提供了大量的静态泛型方法
  2. public static void Sort<T>(T[] array);
  3. public static void Sort<T>(T[] array,IComparer<T> comparer);
  4. ....
  5. public static void Main(){
  6. Byte[] byteArray = new Byte[]{ 5, 1, 4, 2, 3);
  7. Array.Sort<Byte>(byteArray);
  8. }

泛型基础结构

开放类型和封闭类型

CLR为应用程序使用的各种类型创建成为类型对象的内部数据结构。具有泛型参数类型的类型仍然是类型,成为开放类型,CLR同样为它创建内部的类型对象,但CLR禁止构造开放类型的任何实例,这类似与CLR禁止构造接口类型的实例。在使用时为所有类型参数都传递了实际的数据类型,类型就成为封闭类型。CLR允许构造封闭类型的实例。
书中部分代码:

  1. internal static class OpenTypes {
  2. public static void Go() {
  3. Object o = null;
  4. // Dictionary<,> is an open type having 2 type parameters,开放类型,有两个类型参数
  5. Type t = typeof(Dictionary<,>);
  6. // Try to create an instance of this type (fails),创建开放类型的实例(失败)
  7. o = CreateInstance(t);
  8. Console.WriteLine();
  9. // DictionaryStringKey<> is an open type having 1 type parameter,开放类型,有一个类型参数
  10. t = typeof(DictionaryStringKey<>);
  11. // Try to create an instance of this type (fails),创建开放类型的实例(失败)
  12. o = CreateInstance(t);
  13. Console.WriteLine();
  14. // DictionaryStringKey<Guid> is a closed type,封闭类型
  15. t = typeof(DictionaryStringKey<Guid>);
  16. // Try to create an instance of this type (succeeds) ,创建封闭类型的实例(成功)
  17. o = CreateInstance(t);
  18. // Prove it actually worked
  19. Console.WriteLine("Object type=" + o.GetType());
  20. }

泛型类型和继承

泛型类型仍然是类型,所以能从其他任何类型派生。新的类型对象从泛型类型派生自的那个类型派生。例如,List<T>从Object派生,所以List<string>List<Guid>也从Object派生。

  1. private static void SameDataLinkedList() {
  2. //在这个链表中,只能包含char类型的数据
  3. Node<Char> head = new Node<Char>('C');
  4. head = new Node<Char>('B', head);
  5. head = new Node<Char>('A', head);
  6. Console.WriteLine(head.ToString());
  7. }
  8. private static void DifferentDataLinkedList() {
  9. //这里可以包含String和DataTime等不同类型的数据
  10. Node head = new TypedNode<Char>('.');
  11. head = new TypedNode<DateTime>(DateTime.Now, head);
  12. head = new TypedNode<String>("Today is ", head);
  13. Console.WriteLine(head.ToString());
  14. }
  15. private sealed class Node<T> {
  16. public T m_data;
  17. public Node<T> m_next;
  18. public Node(T data)
  19. : this(data, null) {
  20. }
  21. public Node(T data, Node<T> next) {
  22. m_data = data; m_next = next;
  23. }
  24. public override String ToString() {
  25. return m_data.ToString() +
  26. ((m_next != null) ? m_next.ToString() : null);
  27. }
  28. }
  29. private class Node {
  30. protected Node m_next;
  31. public Node(Node next) {
  32. m_next = next;
  33. }
  34. }
  35. private sealed class TypedNode<T> : Node {
  36. public T m_data;
  37. public TypedNode(T data)
  38. : this(data, null) {
  39. }
  40. public TypedNode(T data, Node next)
  41. : base(next) {
  42. m_data = data;
  43. }
  44. public override String ToString() {
  45. return m_data.ToString() +
  46. ((m_next != null) ? m_next.ToString() : null);
  47. }
  48. }

所以更好的是第二种链表构造方法,先定义非泛型Node基类,再定义泛型TyepedNode类

代码爆炸

CLR腰围每种不同的方法、类型组合生成本机代码,可能是应用程序的工作集显著增大,这个现象称为代码爆炸
两种优化措施:

1、首先假如为特定的类型实参调用了一个方法,以后再用相同的类型实参调用这个方法,CLR只会为这个方法、类型组合编译一次代码。
2、CLR认为所有的引用类型实参都完全相同,所以代码能够共享。

泛型接口

引用类型和值类型可以指定类型实参实现泛型接口,也可以保持类型实参的为指定状态实现泛型接口。

  1. public interface IEnumerator<T>:IDisposable,IEnumerator{
  2. }
  3. //指定了类型实参为Point
  4. internal sealed class Triangle:IEnumerator<Point>{
  5. ....
  6. }
  7. //为指定类型实参
  8. internal sealed class ArrayEnumerator<T:IEnumerator<T>{
  9. ...
  10. }

泛型委托

FCL中预先定义了泛型委托Action和Func等供开发人员进行使用。

委托和接口的逆变和协变型类型实参

泛型委托参数可以是一下任何一种形式:

不变量
逆变量-C#中使用in关键字标记,只出现在输入位置,比如作为方法的参数,可以更改为它的派生类
协变量-C#中使用out关键字标记,只出现在输出位置,比如方法的返回值,可以更改为它的基类

简言之:协变性指定返回类型的兼容性,逆变性指定参数的兼容性。

例如:

public delegate TResult Func<in T,out TResult>(T arg)

像下面这样声明一个变量:

Func<Object,ArgumentException> fn1 = null;
Func<String,Exception> fn2 = fn1;//不需要转型

泛型方法

泛型方法一个很好的例子是swap方法

  1. private static void Swap<T>(ref T o1, ref T o2) {
  2. T temp = o1;
  3. o1 = o2;
  4. o2 = temp;
  5. }
  6. private static void CallingSwap() {
  7. Int32 n1 = 1, n2 = 2;
  8. Console.WriteLine("n1={0}, n2={1}", n1, n2);
  9. Swap<Int32>(ref n1, ref n2);
  10. Console.WriteLine("n1={0}, n2={1}", n1, n2);
  11. String s1 = "Aidan", s2 = "Kristin";
  12. Console.WriteLine("s1={0}, s2={1}", s1, s2);
  13. Swap<String>(ref s1, ref s2);
  14. Console.WriteLine("s1={0}, s2={1}", s1, s2);
  15. }

C#编译器支持类型推断

  1. Int32 n1 = 1, n2 = 2;
  2. Swap(ref n1, ref n2); // Calls Swap<Int32>
  3. String s1 = "Aidan";
  4. Object s2 = "Grant";
  5. Swap(ref s1, ref s2);//调用失败,不能推断类型
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注