C#学习笔记10——C#泛型编程

来源:互联网 发布:风险矩阵分析法 公式 编辑:程序博客网 时间:2024/05/16 15:47

C#泛型编程

 

"一次编码,多次使用",这就是引入泛型的根源。在以前的C++中称为模板,C#泛型通过算法和数据结构支持独立编码。例如,泛型列表意味着,你不必再重写一个强类型集合。在本文中,作者将向你展示定义和使用泛型是多么容易的事情-请注意,长期以来泛型一直被认为是最高级和最困难的术语。

一、简介

泛型现在在任何一种语言中都被认为是一个高级的强有力的术语。当我在C++中第一次接触模板时,我对之有些疑惑。之后,我读了Bjarne Stroustrop的《The Design and Evolution of C++》,才发现模板的使用就象C中的宏和用之来取代的简单串替换模板一样容易。其实,模板和泛型是相同的东西-尽管它们的实现稍微不同。

C#泛型支持在使用点处才定义算法及其数据类型。在C#的一些早期版本中,我们可以证明没有泛型也可以工作,因为每种类型都是派生于一个公共基类型-object。这意味着程序员可以基于object类型定义一个栈类并且把一切东西放到该栈上(因为一切都派生于object)。然而,一个object栈意味着,Customer对象,Integer对象以及假想的对象都能被放置到同一个栈的实例上。结果是,开发者要子类化数据类型来把数据类型绑定到他们要与之交互的东西上去。例如,在编写定制的商业对象时,我们就建议定义派生于System.Collections.CollectionBase的强类型集合。原因很简单:基于object定义一切被认为是弱类型定义。

业界的高手们在数十年前就确信强类型优于弱类型,所以.NET最终支持强类型,这看上去是很自然的事情。强类型算法当然建议类型化参数-这正是我们在泛型中所用的东西。

十几年来,我们一直在使用字母T作为类型化参数的名字。这样,在任何泛型类使用者所提供的数据类型的地方,你都能够找到T。使用泛型的关键仅仅是提供这个T。定义泛型的关键在于实现一个方法或类,并且用特定数据类型来替换掉T。

C#泛型支持另外一些提炼。例如,一个方法或类可以有多个参数化的类型并且C#泛型还支持WHERE约束-它用来具体要求类型化参数的类型。例如,如果一个泛型类型必须实现接口IDisposable,那么C#泛型是支持实现这一限制的。在文章的最后我们还要看一下约束问题。

二、使用泛型集合

有些人问我"面向对象编程(OOP)的承诺在哪里?",我的回答是应该从两个方面来看OOP:你所使用的OOP和你创建的OOP。如果我们简单地看一下如果没有如例如Microsoft的.NET,Borland的VCL,以及所有的第三方组件这样的OO框架,那么很多高级的应用程序几乎就无法创建。所以,我们可以说OOP已经实现了它的承诺。不错,生产好的OOP代码是困难的并且可能是极具挫败性的;但是记住,你不必须一定要通过OOP来实现你的目标。因此,下面首先让我们看一下泛型的使用。

当你用Visual Studio或C# Express等快速开发工具创建工程时,你会看到对于System.Collections.Generic命名空间的参考引用。在这个命名空间中,存在若干泛型数据结构-它们都支持类型化的集合,散列,队列,栈,字典以及链表等。为了使用这些强有力的数据结构,你所要做的仅是提供数据类型。

列表1显示出我们定义一个强类型集合的Customer对象是很容易的。

列表1 这个控制台应用程序包含一个Customer类和一个基于List< T>的强类型集合Customers。

usingSystem; 

usingSystem.Collections.Generic; 

usingSystem.Text; 

namespaceGenerics{ 

 class Program{ 

staticvoid Main(string[] args){ 

 List< Customer> customers =new List< Customer>(); 

 customers.Add(newCustomer("Motown-Jobs")); 

 customers.Add(newCustomer("Fatman's")); 

 foreach (Customer c incustomers) 

 Console.WriteLine(c.CustomerName); 

 Console.ReadLine(); 

 } 

 public class Customer{ 

privatestring customerName = ""; 

publicstring CustomerName{ 

 get { return customerName; } 

 set { customerName = value; } 

publicCustomer(string customerName){ 

 this.customerName =customerName; 

 } 

}

 

注意,我们有一个强类型集合-List<Customer>-对这个集合类本身来说不需要写一句代码。如果我们想要扩展列表customer,我们可以通过从List< Customer>继承而派生一个新类。

三、 实现一个泛型类

一种合理的实现某种新功能的方法是在原有的事物上进一步构建。我们已经了解强类型集合,并知道一种不错的用来构建泛型类的技术是使用一个特定类并删除数据类型。也就是说,让我们定义一个强类型集合CustomerList,并且来看一下它要把什么东西转化成一个泛型类。

列表2定义了一个类CustomerList。后面的部分把CustomerList转化成List< T>。

列表2定义类CustomerList:

usingSystem; 

usingSystem.Collections; 

usingSystem.Text; 

namespaceGenerics{ 

 public class CustomerList :CollectionBase{ 

publicCustomerList() { } 

publicCustomer this[int index]{ 

 get { return(Customer)List[index]; } 

 set { List[index] = value; } 

publicint Add(Customer value) 

{returnList.Add(value);} 

 } 

}

四、 定义类头

如果我们定义一个泛型类,我们需要把类头转化成一个泛型类。所有我们需要做的是命名参数并且把类名改成某种泛型。List< T>只有一个参数T,并且因为我们在以一种向后兼容的方式工作,所以我们知道类名是List。列表3显示出列表2中类的新类头。

列表3 一个泛型类头显示出参数化的参数T。

usingSystem; 

usingSystem.Collections; 

usingSystem.Text; 

namespaceGenerics{ 

publicclass List< T> : CollectionBase {}

五、 实现泛型字段

如果我们需要把任何字段转换成泛型字段,我们将只需简单地把它们的类型改变成T(或该字段所描述的任何参数)。泛型List不需要任何字段,但是假定存在一个私有的整型字段叫foo-我们将把它泛型化。我们将如下重新定义它:

private Tfoo;

当参数T被填充到类中时,List T也将因foo被填充。

六、 定义泛型方法

接下来,我们为所需要的参数化类型定义其它一些特性。这包括属性,方法,和事件。在我们的实例中,在Customer出现的每一处,我们都用参数T替换它。完成后的泛型列表类显示于列表4中。

列表4 一个基于System.Collections.CollectionBase的轻量级的参数化泛型列表类。

usingSystem; 

usingSystem.Collections; 

usingSystem.Text; 

namespaceGenerics{ 

 public class List< T> :CollectionBase { 

publicList(){ } 

public Tthis[int index] { 

 get { return (T)List[index];} 

 set { List[index] = value; } 

publicint Add(T value) { 

 return List.Add(value); 

 } 

}

为了测试该定制列表,注释掉使用System.Collections.Generic命名空间一句并且把列表4中的List< T>使用在列表1的代码中;它将以同样的方式工作。

全面地修改.NET的List< T>是不必要的而且它也包含远比我们的示例多得多的特性;但是列表4显示出这种机制对于定义定制泛型类是多么容易。

七、 增加类型约束

最后要讨论的是约束问题。约束被应用于类或其它特性上并且使用下面的语法:

Where T :constraint_type

例如,任何我们想要通过using语句所使用的,如一个SqlDataReader,必须实现Idisposable接口。这是因为如下方式使用的using语句:

using(Formf = new Form()){...}

就象一个try..finally块一样工作-总是清除新创建的资源。其工作原理很简单,只需要CLR针对在该using语句中创建的对象发出一个到IDisposable.Dispose的调用即可。例如,在上面这一句中,一个新的表单被创建,并且在using语句退出之前即调用Form.Dispose。

要对一个泛型类施加以确保该类实现了接口IDisposable,我们将添加先行词where T:Idisposable。在列表4中的泛型列表上施加约束后,我们将重新修改列表4如下面的列表5所示。

列表5 增加一个约束到泛型类以确保我们的List< T>中的所有的值T实现接口Idisposable。

usingSystem; 

usingSystem.Collections; 

usingSystem.Text; 

namespaceGenerics{ 

 public class List< T> :CollectionBase where t : IDisposable{ 

publicList(){ } 

public Tthis[int index]{ 

 get { return (T)List[index];} 

 set { List[index] = value; } 

publicint Add(T value){return List.Add(value);} 

 } 

}

先行词where的值可以是类,接口,结构,实现一个无参的公共构造器或有一个特定的基类的类。详见有关帮助文档。

八、泛型委托:

泛型委托支持在委托返回值和参数上应用参数类型,这些参数类型同样可以附带合法的约束

delegatebool MyDelegate<T>(T value);

classMyClass

{

    static bool F(int i){...}

    static bool G(string s){...}

    static void Main()

    {

        MyDelegate<string> p2 = G;

        MyDelegate<int> p1 = newMyDelegate<int>(F);

    }

}

九、泛型约束:

1、C#泛型要求对“所有泛型类型或泛型方法的类型参数”的任何假定,都要基于“显式的约束”,以维护C#所要求的类型安全。

2、“显式约束”由where子句表达,可以指定“基类约束”,“接口约束”,“构造器约束”,“值类型/引用类型约束”共四种约束。

3、“显式约束”并非必须,如果没有指定“显式约束”,范型类型参数将只能访问System.Object类型中的公有方法。例如:在开始的例子中,定义的那个obj成员变量。比如我们在开始的那个例子中加入一个Test1类,在它当中定义两个公共方法Func1、Func2,如下图:



下面就开始分析这些约束:

基类约束:

class A

{

public void Func1()

{ }

}

class B

{

public void Func2()

{ }

}

class C<S, T>

where S : A

where T : B

{

public C(S s,T t)

{

//S的变量可以调用Func1方法

s.Func1();

//T的变量可以调用Func2方法

t.Func2();

}

}

接口约束:

interface IA<T>

{

T Func1();

}

interface IB

{

void Func2();

}

interface IC<T>

{

T Func3();

}

class MyClass<T, V>

where T : IA<T>

where V : IB, IC<V>

{

public MyClass(T t,V v)

{

//T的对象可以调用Func1

t.Func1();

//V的对象可以调用Func2和Func3

v.Func2();

v.Func3();

}

}

构造器约束:

class A

{

public A()

{ }

}

class B

{

public B(int i)

{ }

}

class C<T> where T: new()

{

T t;

public C()

{

t = newT();

}

}

class D

{

public void Func()

{

C<A> c = new C<A>();

C<B> d = new C<B>();

}

}

    d对象在编译时报错:The type B must have a publicparameterless constructor in order to use it as parameter 'T' in the generictype or method C<T>

    注意:C#现在只支持无参的构造器约束

    此时由于我们为B类型写入了一个有参构造器,使得系统不会再为B自动创建一个无参的构造器,但是如果我们将B类型中加一个无参构造器,那么对象d的实例化就不会报错了。B类型定义如下:

class B

{

public B()

{ }

public B(int i)

{ }

}

值类型/引用类型:

public struct A {}

public class B {}

public class C<T>where T : struct

{

}

C<A> c1 = new C<A>();

C<B> c2 = new C<B>();

    c2对象在编译时报错:The type 'B' must be a non-nullablevalue type in order to use it as parameter 'T' in the generic type or methor'C<T>'

   

总结:

C#泛型的设计是用来减少你重复实现的代码的次数-只需改变数据类型即可。因为抽象数据结构,如队列,栈和列表皆是典型的数据结构,所以存在针对这些东西的泛型类完全可以理解。你可以从.NET中派生大量的值-通过使用现有的泛型类,如在System.Collections.Generic命名空间中的那些。

1、C#的泛型能力由CLR在运行时支持,它既不同于C++在编译时所支持的静态模板,也不同于Java在编译器层面使用“擦拭法”支持的简单的泛型。

2、C#的泛型支持包括类、结构、接口、委托四种泛型类型,以及方法成员。

3、C#的泛型采用“基类,接口,构造器,值类型/引用类型”的约束方式来实现对类型参数的“显式约束”,它不支持C++模板那样的基于签名的隐式约束。

原创粉丝点击