【整理】C#2.0泛型编程之概述、default()方法、别名指定与泛型约束
泛型概述
首先我们看一个在泛型出现之前通用的数据结构示例
通用数据结构类
调用实例
然后我们看看基于上面object解决方案存在的问题
1.由于在实际调用的时候会频繁出现值类型的装箱、拆箱,这将会导致很多垃圾碎片,增加垃圾收集的负担;对于引用类型这方面的问题也不容小觑
2.编译时任务类型都可以转换成object,无法保证运行时类型的安全。请看下面代码所示,编译可通过,运行时报错:
那么有人提出了,既然通用数据结构类用object类型不行,那我可以用特定类型来替换上面结构类中的object类型,来处理特定类型的Stack,这样虽然解决了上面两个问题,但又会出现如下问题:
1.影响工作效率。
因为你首先要预知有好多个特定类型要使用上面通用结构类,然后要根据这些不同的类型写不同的class
2.代码冗余,复用率很低,这是显而易见的
3.一个数据结构的变更要将所有类型的数据结构做相应的修改。
因为要修改所有基于这个数据结构来实现的特定类型的数据结构
4.为了提供不可预知的数据类型的支持,还是要提供object类型接口,这样老问题又会出现
针对上面出现的问题,似乎在原来是没办法解决的问题,但在2.0以后微软推出泛型这一数据结构,可以很好的解决上面的问题。
什么是泛型?
通过泛型可以定义类型安全类,而不损害类型安全、性能或工作效率,我们可以将下面的结构类
修改为
运行时实例化
1.可以使用任何类型声明和实例化
2.声明和实例化是都必须使用一个特定类型来代替一般类型
3.泛型编程模型的优点是:内部算法和操作保持不变,而实际类型可以在使用时指定
泛型是如何实现的?
1.在.Net2.0中,泛型在IL和CLR本身中具有本机支持
2.编译类型时,就像编译其他类型一样,泛型仅保留一个占位符
3.而用特定类型实例化泛型代码,编译时会将泛型替代为特定的实际类型
泛型的好处
1.一次性的开发、测试和部署代码,通过任何类型(包括将来的类型)来重用它
2.编译器支持和类型安全
3.不会强行对值类型进行装箱和拆箱,或者对引用类型进行向下强制类型转换,所以性能得到显著提高。对于值类型,一般提高200%,对于引用类型,也可提高100%(当然整个应用程序的运行效率也可能会提高,也可能不会提高)。
在结构体中使用泛型
定义通用结构体
运行实例
default方法
在针对上面的泛型通用结构类中,如果你不希望在堆栈为空时引发异常,而是希望返回堆栈中存储类型的默认值,就要使用到default方法。此方法主要用于将获取泛型编程模型中的泛型类型参数的默认值
-default(ValueType)=0;
-default(ReferenceType)=null;
举例说明:如上面Stack<T>,用default(T)获取默认值。
多个泛型
单个类型可以指定多个泛型
比如Custom<K,T>,Custom为类型,K为此类型的第一个泛型,T为第二个泛型,若更多的话用,隔开。
泛型别名
在文件头部使用using关键字为特定类型取别名
别名作用范围是整个文件,具体实现
泛型约束-派生约束
为什么要使用泛型约束,先看下面方法
public class LinkedList
{
T Find(K key)
{
Node<K,T> current = m_Head;
while(current.NextNode!=null)
{
//因为编译器不知道K类型的key是否支持"=="运算符,例如在默认情况,结构体就不提供这种实现
if(current.Key==key) //Will not Compile
{
break;
}
else
{
current=current.NextNode;
}
}
return current.Item;
}
}
根据上面注释得知,此方法是不会通过编译的,那么采取什么方法来解决呢,我们就想到了IComparable下面的CompareTo方法,如下:
public interface IComparable
{
int CompareTo(object obj);
}
解决方案如下:
public class LinkedList<K,T> where K:IComparable
{
T Find(K key)
{
Node<K,T> current = m_Head;
while(current.NextNode!=null)
{
//因为编译器不知道K类型的key是否支持"=="运算符,例如在默认情况,结构体就不提供这种实现
if(current.Key.CompareTo(key)==0) //Will not Compile
{
break;
}
else
{
current=current.NextNode;
}
}
return current.Item;
}
}
注:1.where关键字,若上类中的K和T都要约束,那么两个where之间用空格隔开
2.K:IComparable表示K只接受实现了IComparable接口的类型
3.尽管如此,还是无法避免传入值类型的K所带来的装箱问题,原因是IComparable下的CompareTo方法的参数仍是object类型。所以最终的正确代码如下:
public class LinkedList<K,T> where K:IComparable<T>
{
T Find(K key)
{
Node<K,T> current = m_Head;
while(current.NextNode!=null)
{
//因为编译器不知道K类型的key是否支持"=="运算符,例如在默认情况,结构体就不提供这种实现
if(current.Key.CompareTo(key)==0) //Will not Compile
{
break;
}
else
{
current=current.NextNode;
}
}
return current.Item;
}
}
注意:
1.在C#2.0中,所有的派生约束必须放在类的实际派生列表之后
public class LinkedList<K,T> :IEnumerable<T> where K:IComparable<K>
{......}
2.通常只需要在需要的级别定义类的约束,即:哪里编译异常哪里约束。
3.一个泛型参数上约束多个接口(彼此用,分隔)
public class LinkedList<K,T> where K:IComparable<K>,IConvertible
{......}
4.在一个约束中最多只能有一个基类,同时约束的基类不能是密封类或静态类,因为密封类不能被继承,静态类不能被实例化。
5.不能将System.Delegate或System.Array作为泛型基类
6.可以同时约束一个基类和一个或多个接口,但是基类必须首先出现在约束列表中
public class LinkedList<K,T> where K:MyBaseClass,IComparable<K>,IConvertible
{......}
7.C#允许你将另一个一般类型指定为约束
public class LinkeList<K,T> where K:T
{......}
8.自定义基类或接口进行泛型约束
自定义接口
public interface IMyInterface
{......}
public class MyClass<T> where T:IMyInterface
{......}
MyClass<IMyInterface> obj=new MyClass<IMyInterface>();
自定义基类
public class MyOtherClass
{......}
public class MyClass<T> where T:MyOtherClass
{......}
MyClass<MyOtherClass> obj=new MyClass<MyOtherClass>();
9.自定义的基类或接口必须与泛型参数具有一致的可见性
正确的可见性
public class MyBaseClass
{......}
internal class MySubClass<T> where T:MyBaseClass
{......}
错误的可见性
internal class MyBaseClass
{......}
public class MySubClass<T> where T:MyBaseClass
{......}
泛型约束-构造函数约束
假设你要在一个一般类的内部实例化一个新的一般对象。问题在于,C#编译器不知道客户将要使用的类型实参是否具有匹配的构造函数,因而它拒绝编译实例化行。
为了解决该问题,C#运行约束一般类型参数,以使其必须支持公共支持的默认构造函数这是使用new()约束来完成的
class Node<K,T> where T:new()
{
public K key;
public T item;
public Node<K,T> NextNode;
public Node()
{
key=default(K);
item=new T();
NextNode=null;
}
}
注意:
可以将构造函数和派生约束混合起来,前提是构造函数约束要放到约束列表的最后
public class LinkedList<K,T> where K:IComparable<K>,new()
{......}
泛型约束-值/引用类型约束
可以使用struct约束将一般类型参数约束为值类型(例如:int,bool和enum),或任何自定义结构
public class MyClass<T> where T:struct
{......}
可以使用class约束将一般类型参数约束为引用类型(类)
public class MyClass<T> where T:class
{......}
注意
1.不能将引用/值类型约束与基类约束一起使用,因为基类约束涉及到类。
2.不能使用结构和默认构造函数约束,因为默认构造函数约束也涉及到类。
3.虽然你可以使用类和默认构造函数约束,但是这样做没有任何意义。
4.可以将引用/值类型约束与接口约束组合起来,前提是引用/值类型约束必须放在约束列表的开头。