`

泛 型

    博客分类:
  • CLR
 
阅读更多

面向对象的编程方式有一种好处是“代码重用”,泛型是CLR和编程语言提供的一种特殊机制,它支持另一种形式的重用,即“算法重用”。

比如,一个开发人员定义好一个算法,如排序、搜索、交换、比较或者转换等。但是,定义算法的开发人员并不设定该算法要操作什么数据类型;该算法可以应用于不同类型的对象。然后,另一个开发人员只要指定了算法要操作的具体数据类型,就可以开始使用这个现成的算法了。例如,可以用一个排序算法来操作Int32和String等类型的对象,或用一个比较算法来操作DateTime和Version等类型的对象。

CLR允许创建泛型引用类型、泛型值类型、泛型接口、泛型委托、泛型方法,但不允许创建泛型枚举类型。

一个常用的泛型引用类型 List<T>

这个泛型List类的设计者紧跟在类名后添加了一个<T>,表明它操作的是一个未指定的数据类型。

一个开发人员定义泛型类型或方法时,他为类型指定的任何变量(比如T)被称为类型参数。

另一个开发人员使用泛型类型或方法时,指定的具体数据类型被称为类型实参。 如List<DateTime>

 

泛型类型仍然是类型,所以它能从其他任何类型派生。使用一个泛型类型并指定类型实参时,实际是在CLR中定义一个新的类型对象,新的类型对象是从泛型类型派生自的那个类型派生的。换言之,由于List<T>是从Object派生的,所以List<String>、List<Guid>也是从Object派生的。类似的,DictionaryStringKey<TValue>派生自Dictionary<String,TValue>,所以DictionaryStringKey<Guid>派生自Dictionary<String,Guid>

 

泛型类型的同一性

有时一些开发人员可能如下使用泛型(以List<DateTime>为例),为的是简化代码:

internal sealed class DateTimeList:List<DateTime>{}

然后就可以像如下那样使用List<DateTime>了

DateTimeList dt=new DateTimeList();

但运行一下如下代码,我们就会发现这样做会丧失类型的同一性和相等性:

Boolean flag=(typeof(List<DateTime>)==typeof(DateTimeList));   结果是false

为了实现这种想法,C#提供了一种方法,如下:

using DateTimeList=System.Collections.Generic.List<System.DateTime>;

 

泛型方法

定义泛型类、结构、接口时,这些类型中定义的任何方法都可以引用由类型指定的一个类型参数。类型参数可以作为方法的参数,作为方法的返回值,或者作为方法内部定义的一个局部变量来使用。然而,CLR还允许一个方法指定它独有的类型参数,同样这些类型参数可用于参数、返回值或者局部变量。在下面的例子中,一个类型定义了一个类型参数,一个方法则定义了它自己的专用类型参数:

internal sealed class GenericType<T>{

  Private T m_value;

  public GenericType(T value){mvalue=value;}

  public TOutput Converter<TOutput>(){

     TOutput result=(TOutput)Convert.ChangeType(m_value,typeof(TOutput));

     return result;

  }

}

C#编译器支持在调用一个泛型方法时进行类型判断。这意味着编译器会在调用一个泛型方法时自动判断要使用的类型,而不需要指定方法的<T>的T的类型:

private void Swap<T>(ref T o1,ref T o2)

{

    T temp=o1;

    o1=o2;

    02=temp;

}

private void CallSwap()

{

   int32 n1=1,n2=2;

   Swap(ref n1,ref n2);  //调用Swap<Int32>

   string s1="Bill"; Object s2="Gates";

   Swap(ref s1,ref s2);  //错误,不能判断类型

}

执行类型判断时,C#使用变量的数据类型,而不是变量所引用的对象的类型,所以在第二个Swap调用中,变量S1是string类型,而变量S2是Object类型(尽管它所引用的对象时string类型),所以不能判断调用Swap<string>还是Swap<Object>

 

可验证性和约束

编译泛型代码时,C#编译器会对它进行分析,确保代码适用于已有或将来可能定义的任何类型,看下面代码:

private Boolean Method<T>(T o)

{

   T temp=o;

   Console.WriteLine(o.ToString());

   Boolean b=temp.Equals(0);

   return b;

}

这个方法适用于任何类型,因为所有类型都支持对Object类型定义的方法(这里是ToString和Equals)

再来看看如下代码:

private T Min<T>(T o1,T o2)

{

   if(o1.CompareTo(o2)<0) return o1;

   return o2;

}

C#编译器将不能编译上述代码,它不能保证这个方法适用于所有类型,因为许多类型都没有提供CompareTo方法。

如此的话泛型将没什么用,但幸好,编译器和CLR支持一个称为约束的机制,看下面新版本的Min方法:

public T Min<T>(T o1,To2) where T:IComparable<T>

{

   if(o1.CompareTo(o2)<0) return o1;

   return o2;

}

where关键字告诉编译器,为T指定的任何类型都必须实现同类型(T)的泛型IComparable接口

现在,编译器要负责保证类型实参符合指定的约束:

public void CallMin()

{

   string o1="Bill"; Object o2="Gates";

   Object oMin=Min<Object>(o1,o2);

}

上述代码将报错,因为Object没事实现IComparable<Object>接口,Object没有实现任何接口。、

CLR不允许基于类型参数名称或约束进行重载,只能基于类型参数个数来进行重载:

internal class AType{}    1

internal class AType<T>{}    2

internal class AType<T1,T2>{}    3

internal class AType<T> where T:IComparable<T>{}  与2冲突

internal class AType<T3,T4>{}  与3冲突

重写一个虚泛型方法时,重写的方法必须指定相同数量的类型参数,而且这些类型参数会继承在基类方法上指定的约束。事实上,根本不允许为重写方法的类型参数指定任何约束:

internal class Base

{

   public virtual void M<T1,T2> where T1:struct where T2:class{}

}

internal class Derived:Base

{

   public override void M<T3,T4> where T3:EventArgs where T4:class{}

   会报错,类型参数会继承在基类方法上指定的约束,不允许为重写方法的类型参数指定任何约束

}

主要约束

类型参数可以指定零个或一个主要约束,有两个特殊的主要约束:class和struct

class约束向编译器承诺一个指定的类型实参是引用类型:

internal class AType<T> where T:class

{

    public void M(){T temp=null;}   //所有引用类型的变量都能设为null

}

struct约束向编译器承诺一个指定的类型实参是值类型:

internal class AType<T> where T:struct

{

   public void M(){return new T();}  //所有值类型都隐藏一个公共无参构造器,而有的引用类型没有公共无参构造器

}

次要约束

次要约束又分两种,第一种用的比较多就是接口类型约束(可指定零个或多个)

指定一个接口类型约束时,是向编译器承诺一个指定的类型实参是实现了接口的一个类型,由于能指定多个接口约束,所以为类型实参指定的类型必须实现了所有接口约束(以及所有主要约束,如果指定了的话)

第二种用的比较少就是类型参数约束,意思是在指定的类型实参之间必须存在一个关系:

private List<TBase> Convert<T,TBase>(IList<T> list) where T:TBase{ ... }

以上方法指定了两个类型参数,其中T参数由TBase类型参数约束,意味着不管T指定什么类型实参,这个类型实参都必须兼容于为TBase指定的类型实参。

构造器约束

一个类型参数可以指定零个或一个构造器参数。指定构造器参数是向编译器承诺:一个指定的类型实参是实现了公共无参构造器的一个抽象类型:

internal class AType<T> where T:new()

{

    public T Menthod()

    {

        //所有值类型都隐式有一个公共无参构造器

        //约束要求指定的任何引用类型也要有一个公共无参构造器

        return new T();

    }

}

 

C#编译器允许使用default将一个泛型类型变量设为默认值:

private void SetIt<T>()

{

   T temp=default(T);

}

以上代码告诉C#编译器和CLR的JIT编译器,如果T是一个引用类型,就将temp设为null;如果T是值类型,就将temp设为0。

 

将一个泛型变量与null进行比较:

private void CompareIt<T>(T obj)

{

   if(obj==null){...}

}

由于T没有进行约束,如果T是一个值类型,...处的代码永远不会被执行

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics