@kuier1992
2015-10-28T23:02:25.000000Z
字数 4178
阅读 2291
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 worked
Console.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{
}
//指定了类型实参为Point
internal 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);//调用失败,不能推断类型