【整理】C#2.0泛型编程之概述、default()方法、别名指定与泛型约束

泛型概述

    首先我们看一个在泛型出现之前通用的数据结构示例

    通用数据结构类

Code

   调用实例

Code

    然后我们看看基于上面object解决方案存在的问题

    1.由于在实际调用的时候会频繁出现值类型的装箱、拆箱,这将会导致很多垃圾碎片,增加垃圾收集的负担;对于引用类型这方面的问题也不容小觑

    2.编译时任务类型都可以转换成object,无法保证运行时类型的安全。请看下面代码所示,编译可通过,运行时报错:

Code

    那么有人提出了,既然通用数据结构类用object类型不行,那我可以用特定类型来替换上面结构类中的object类型,来处理特定类型的Stack,这样虽然解决了上面两个问题,但又会出现如下问题:

    1.影响工作效率。

         因为你首先要预知有好多个特定类型要使用上面通用结构类,然后要根据这些不同的类型写不同的class

    2.代码冗余,复用率很低,这是显而易见的

    3.一个数据结构的变更要将所有类型的数据结构做相应的修改。

         因为要修改所有基于这个数据结构来实现的特定类型的数据结构

    4.为了提供不可预知的数据类型的支持,还是要提供object类型接口,这样老问题又会出现

    针对上面出现的问题,似乎在原来是没办法解决的问题,但在2.0以后微软推出泛型这一数据结构,可以很好的解决上面的问题。

什么是泛型?

    通过泛型可以定义类型安全类,而不损害类型安全、性能或工作效率,我们可以将下面的结构类

Code

修改为

Code

运行时实例化

1.可以使用任何类型声明和实例化

2.声明和实例化是都必须使用一个特定类型来代替一般类型

3.泛型编程模型的优点是:内部算法和操作保持不变,而实际类型可以在使用时指定

Code

泛型是如何实现的?

    1.在.Net2.0中,泛型在IL和CLR本身中具有本机支持

    2.编译类型时,就像编译其他类型一样,泛型仅保留一个占位符

    3.而用特定类型实例化泛型代码,编译时会将泛型替代为特定的实际类型

泛型的好处

    1.一次性的开发、测试和部署代码,通过任何类型(包括将来的类型)来重用它

    2.编译器支持和类型安全

    3.不会强行对值类型进行装箱和拆箱,或者对引用类型进行向下强制类型转换,所以性能得到显著提高。对于值类型,一般提高200%,对于引用类型,也可提高100%(当然整个应用程序的运行效率也可能会提高,也可能不会提高)。

在结构体中使用泛型

    定义通用结构体

Code

    运行实例

Code
Code

default方法

    在针对上面的泛型通用结构类中,如果你不希望在堆栈为空时引发异常,而是希望返回堆栈中存储类型的默认值,就要使用到default方法。此方法主要用于将获取泛型编程模型中的泛型类型参数的默认值

    -default(ValueType)=0;

    -default(ReferenceType)=null;

    举例说明:如上面Stack<T>,用default(T)获取默认值。

多个泛型

    单个类型可以指定多个泛型

    比如Custom<K,T>,Custom为类型,K为此类型的第一个泛型,T为第二个泛型,若更多的话用,隔开。

泛型别名

    在文件头部使用using关键字为特定类型取别名

    别名作用范围是整个文件,具体实现

Code

泛型约束-派生约束

    为什么要使用泛型约束,先看下面方法

    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.可以将引用/值类型约束与接口约束组合起来,前提是引用/值类型约束必须放在约束列表的开头。

posted @ 2009-06-07 17:08  网络渔夫  阅读(792)  评论(2编辑  收藏  举报