@kuier1992
2015-10-28T15:02:25.000000Z
字数 4178
阅读 2418
C#
昨天写了委托,今天再把泛型给总结一下。还是按照书本的来,也当是为了将来面试而学习。
面向对象编程的好处之一是"代码重用",它极大地提高了开发效率。也就是说,可以派生出一个类,让它继承基类的所有能力。派生类只需要重写虚方法、或添加一些新方法,就可以定制派生类的行为,使之满足开发人员的要求。而泛型支持另一种形式的代码重用,即“算法重用”
CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型。此外,CLR还允许创建泛型接口和泛型委托。
如在C#中广泛使用的List<T>,泛型List类的设计者紧接在类名后添加了一个<T>,表明它操作的是一个未指定的数据类型。T被称为类型参数,而其他开发人员为了使用泛型,要制定具体操作类型。使用泛型是指定的具体操作类型就被称为类型实参。如:开发人员可指定一个DataTime类型实参来使用List<DataTime>
泛型的优势总结:
源代码保护-使用泛型算法的开发人员不需要访问算法的源代码;
类型安全-指定类型参数时只有相符的类型实参才可以应用泛型算法;
更清晰的代码-减少了强制类型转换;
更加的性能-不需要频繁的装箱、拆箱;
泛型最明显的应用应该就是集合类。FCL在System.Collections.Generic和System.Collections.ObjectModel命名空间提供了多个泛型集合类,System.Collections.Concurrent命名空间则提供了线程安全的泛型集合类。Microsoft建议使用泛型集合类。
集合类实现了许多接口,所以放入集合中的对象可实现接口来执行搜索和排序等操作。
简单使用
//Array中提供了大量的静态泛型方法public static void Sort<T>(T[] array);public static void Sort<T>(T[] array,IComparer<T> comparer);....public static void Main(){Byte[] byteArray = new Byte[]{ 5, 1, 4, 2, 3);Array.Sort<Byte>(byteArray);}
CLR为应用程序使用的各种类型创建成为类型对象的内部数据结构。具有泛型参数类型的类型仍然是类型,成为开放类型,CLR同样为它创建内部的类型对象,但CLR禁止构造开放类型的任何实例,这类似与CLR禁止构造接口类型的实例。在使用时为所有类型参数都传递了实际的数据类型,类型就成为封闭类型。CLR允许构造封闭类型的实例。
书中部分代码:
internal static class OpenTypes {public static void Go() {Object o = null;// Dictionary<,> is an open type having 2 type parameters,开放类型,有两个类型参数Type t = typeof(Dictionary<,>);// Try to create an instance of this type (fails),创建开放类型的实例(失败)o = CreateInstance(t);Console.WriteLine();// DictionaryStringKey<> is an open type having 1 type parameter,开放类型,有一个类型参数t = typeof(DictionaryStringKey<>);// Try to create an instance of this type (fails),创建开放类型的实例(失败)o = CreateInstance(t);Console.WriteLine();// DictionaryStringKey<Guid> is a closed type,封闭类型t = typeof(DictionaryStringKey<Guid>);// Try to create an instance of this type (succeeds) ,创建封闭类型的实例(成功)o = CreateInstance(t);// Prove it actually workedConsole.WriteLine("Object type=" + o.GetType());}
泛型类型仍然是类型,所以能从其他任何类型派生。新的类型对象从泛型类型派生自的那个类型派生。例如,List<T>从Object派生,所以List<string>和List<Guid>也从Object派生。
private static void SameDataLinkedList() {//在这个链表中,只能包含char类型的数据Node<Char> head = new Node<Char>('C');head = new Node<Char>('B', head);head = new Node<Char>('A', head);Console.WriteLine(head.ToString());}private static void DifferentDataLinkedList() {//这里可以包含String和DataTime等不同类型的数据Node head = new TypedNode<Char>('.');head = new TypedNode<DateTime>(DateTime.Now, head);head = new TypedNode<String>("Today is ", head);Console.WriteLine(head.ToString());}private sealed class Node<T> {public T m_data;public Node<T> m_next;public Node(T data): this(data, null) {}public Node(T data, Node<T> next) {m_data = data; m_next = next;}public override String ToString() {return m_data.ToString() +((m_next != null) ? m_next.ToString() : null);}}private class Node {protected Node m_next;public Node(Node next) {m_next = next;}}private sealed class TypedNode<T> : Node {public T m_data;public TypedNode(T data): this(data, null) {}public TypedNode(T data, Node next): base(next) {m_data = data;}public override String ToString() {return m_data.ToString() +((m_next != null) ? m_next.ToString() : null);}}
所以更好的是第二种链表构造方法,先定义非泛型Node基类,再定义泛型TyepedNode类。
CLR腰围每种不同的方法、类型组合生成本机代码,可能是应用程序的工作集显著增大,这个现象称为代码爆炸
两种优化措施:
1、首先假如为特定的类型实参调用了一个方法,以后再用相同的类型实参调用这个方法,CLR只会为这个方法、类型组合编译一次代码。
2、CLR认为所有的引用类型实参都完全相同,所以代码能够共享。
引用类型和值类型可以指定类型实参实现泛型接口,也可以保持类型实参的为指定状态实现泛型接口。
public interface IEnumerator<T>:IDisposable,IEnumerator{}//指定了类型实参为Pointinternal sealed class Triangle:IEnumerator<Point>{....}//为指定类型实参internal sealed class ArrayEnumerator<T:IEnumerator<T>{...}
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方法
private static void Swap<T>(ref T o1, ref T o2) {T temp = o1;o1 = o2;o2 = temp;}private static void CallingSwap() {Int32 n1 = 1, n2 = 2;Console.WriteLine("n1={0}, n2={1}", n1, n2);Swap<Int32>(ref n1, ref n2);Console.WriteLine("n1={0}, n2={1}", n1, n2);String s1 = "Aidan", s2 = "Kristin";Console.WriteLine("s1={0}, s2={1}", s1, s2);Swap<String>(ref s1, ref s2);Console.WriteLine("s1={0}, s2={1}", s1, s2);}
C#编译器支持类型推断
Int32 n1 = 1, n2 = 2;Swap(ref n1, ref n2); // Calls Swap<Int32>String s1 = "Aidan";Object s2 = "Grant";Swap(ref s1, ref s2);//调用失败,不能推断类型