深入浅出.NET泛型编程.docx_第1页
深入浅出.NET泛型编程.docx_第2页
深入浅出.NET泛型编程.docx_第3页
深入浅出.NET泛型编程.docx_第4页
深入浅出.NET泛型编程.docx_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

深入浅出.NET泛型编程前言.NET 2.0中泛型的出现是一个令人激动的特征。但是,什么是泛型?你需要它们吗?你会在自己的应用软件中使用它们?在本文中,我们将回答这些问题并细致地分析泛型的使用,能力及其局限性。类型安全.NET中的许多语言如C#,C+和VB.NET(选项strict为on)都是强类型语言。作为一个程序员,当你使用这些语言时,总会期望编译器进行类型安全的检查。例如,如果你把对一个Book类型的引用转换成一个Vehicle型的引用,编译器将告诉你这样的cast是无效的。然而,当谈到.NET 1.0和1.1中的集合时,它们是无助于类型安全的。请考虑一个ArrayList的例子,它拥有一个对象集合-这允许你把任何类型的对象放于该ArrayList中。让我们看一下例1中的代码。例1.缺乏类型安全的ArrayListusing System;using System.Collections;namespace TestAppclass TestSTAThreadstatic void Main(string args)ArrayList list = new ArrayList();list.Add(3);list.Add(4);/list.Add(5.0);int total = 0;foreach(int val in list)total = total + val;Console.WriteLine(Total is 0, total);本例中,我们建立了一个ArrayList的实例,并把3和4添加给它。然后我循环遍历该ArrayList,从中取出整型值然后把它们相加。这个程序将产生结果Total is 7。现在,如果我注释掉下面这句: list.Add(5.0);程序将产生如下的运行时刻异常:Unhandled Exception: System.InvalidCastException: Specified cast is not valid.AtTestApp.Test.Main(Stringargs)in :workareatestappclass1.cs:line 17哪里出错了呢?记住ArrayList拥有一个集合的对象。当你把3加到ArrayList上时,你已把值3装箱了。当你循环该列表时,你是把元素拆箱成int型。然而,当你添加值5.0时,你在装箱一个double型值。在第17行,那个double值被拆箱成一个int型。这就是失败的原因。注意:上面的实例,如果是用VB.NET书写的话,是不会失败的。原因在于,VB.NET不使用装箱机制,它激活一个把该double转换成整型的方法。但是,如果ArrayList中的值是不能转换成整型的,VB.NET代码还会失败。作为一个习惯于使用语言提供的类型安全的程序员,你希望这样的问题在编译期间浮出水面,而不是在运行时刻。这正是泛型产生的原因。3. 什么是泛型?泛型允许你在编译时间实现类型安全。它们允许你创建一个数据结构而不限于一特定的数据类型。然而,当使用该数据结构时,编译器保证它使用的类型与类型安全是相一致的。泛型提供了类型安全,但是没有造成任何性能损失和代码臃肿。在这方面,它们很类似于C+中的模板,不过它们在实现上是很不同的。4. 使用泛型集合.NET 2.0的System.Collections.Generics 命名空间包含了泛型集合定义。各种不同的集合/容器类都被参数化了。为使用它们,只需简单地指定参数化的类型即可。请看例2:例2.类型安全的泛型列表Listint aList = new Listint();aList.Add(3);aList.Add(4);/ aList.Add(5.0);int total = 0;foreach(int val in aList)total = total + val;Console.WriteLine(Total is 0, total);在例2中,我编写了一个泛型的列表的例子,在尖括号内指定参数类型为int。该代码的执行将产生结果Total is 7。现在,如果我去掉语句doubleList.Add(5.0)的注释,我将得到一个编译错误。编译器指出它不能发送值5.0到方法Add(),因为该方法仅接受int型。不同于例1,这里的代码实现了类型安全。5. CLR对于泛型的支持泛型不仅是一个语言级上的特征。.NET CLR能识别出泛型。在这种意义上说,泛型的使用是.NET中最为优秀的特征之一。对每个用于泛型化的类型的参数,类也同样没有脱离开微软中间语言(MSIL)。换句话说,你的配件集仅包含你的参数化的数据结构或类的一个定义,而不管使用多少种不同的类型来表达该参数化的类型。例如,如果你定义一个泛型类型MyListT,仅仅该类型的一个定义出现在MSIL中。当程序执行时,不同的类被动态地创建,每个类对应该参数化类型的一种类型。如果你使用MyListint和MyListdouble,有两种类即被创建。当你的程序执行时,让我们进一步在例3中分析这一点。例3.创建一个泛型类/MyList.cs#region Using directivesusing System;using System.Collections.Generic;using System.Text;#endregionnamespace CLRSupportExamplepublic class MyListTprivate static int objCount = 0;public MyList()objCount+; public int Countgetreturn objCount; /Program.cs#region Using directivesusing System;using System.Collections.Generic;using System.Text;#endregionnamespace CLRSupportExampleclass SampleClass class Programstatic void Main(string args)MyListint myIntList = new MyListint();MyListint myIntList2 = new MyListint();MyListdouble myDoubleList = new MyListdouble();MyListSampleClass mySampleList = new MyListSampleClass();Console.WriteLine(myIntList.Count);Console.WriteLine(myIntList2.Count);Console.WriteLine(myDoubleList.Count);Console.WriteLine(mySampleList.Count);Console.WriteLine(new MyListsampleclass().Count);Console.ReadLine();该例中,我创建了一个称为MyList泛型类。为把它参数化,我简单地插入了一个尖括号。在内的T代表了实际的当使用该类时要指定的类型。在MyList类中,定义了一个静态字段objCount。我在构造器中增加它的值。因此我能发现使用我的类的用户共创建了多少个那种类型的对象。属性Count返回与被调用的实例同类型的实例的数目。在Main()方法,我创建了MyListint的两个实例,一个MyListdouble的实例,还有两个MyListSampleClass的实例-其中SampleClass是我已定义了的类。问题是:Count(上面的程序的输出)的值该是多少?在你继阅读之前,试一试回答这个问题。解决了上面的问题?你得到下列的答案了吗?22112前面两个2对应MyListint,第一个1对应MyListdouble,第二个1对应MyListSampleClass-在此,仅创建一个这种类型的实例。最后一个2对应MyListSampleClass,因为代码中又创建了这种类型的另外一个实例。上面的例子说明MyListint是一个与MyListdouble不同的类,而MyListdouble又是一个与MyListSampleClass不同的类。因此,在这个例中,我们有四个类:MyList: MyListT,MyListint,MyListdouble和MyListX。注意,虽然有4个MyList类,但仅有一个被存储在MSIL。怎么能证明这一点?请看图1显示出的使用工具ildasm.exe生成的MSIL代码。图 1.例3的MSIL6. 泛型方法除了有泛型类,你也可以有泛型方法。泛型方法可以是任何类的一部分。让我们看一下例4:例4.一个泛型方法public class Programpublic static void CopyT(ListT source, ListT destination)foreach (T obj in source)destination.Add(obj);static void Main(string args)Listint lst1 = new Listint();lst1.Add(2);lst1.Add(4);Listint lst2 = new Listint();Copy(lst1, lst2);Console.WriteLine(lst2.Count);Copy()方法就是一个泛型方法,它与参数化的类型T一起工作。当在Main()中激活Copy()时,编译器根据提供给Copy()方法的参数确定出要使用的具体类型。7. 无限制的类型参数如果你创建一个泛型数据结构或类,就象例3中的MyList,注意其中并没有约束你该使用什么类型来建立参数化类型。然而,这带来一些限制。如,你不能在参数化类型的实例中使用象=,!=或等运算符,如:if (obj1 = obj2) 象=和!=这样的运算符的实现对于值类型和引用类型都是不同的。如果随意地允许之,代码的行为可能很出乎你的意料。另外一种限制是缺省构造器的使用。例如,如果你编码象new T(),会出现一个编译错,因为并非所有的类都有一个无参数的构造器。如果你真正编码象new T()来创建一个对象,或者使用象=和!=这样的运算符,情况会是怎样呢?你可以这样做,但首先要限制可被用于参数化类型的类型。读者可以自己先考虑如何实现之。8. 约束机制及其优点一个泛型类允许你写自己的类而不必拘泥于任何类型,但允许你的类的使用者以后可以指定要使用的具体类型。通过对可能会用于参数化的类型的类型施加约束,这给你的编程带来很大的灵活性-你可以控制建立你自己的类。让我们分析一个例子:例5.需要约束:代码不会编译成功public static T MaxT(T op1, T op2) if (op1.CompareTo(op2) 0)return op1;return op2;例5中的代码将产生一个编译错误:Error 1 T does not contain a definition for CompareTo假定我需要这种类型以支持CompareTo()方法的实现。我能够通过加以约束-为参数化类型指定的类型必须要实现IComparable接口-来指定这一点。例6中的代码就是这样:例6.指定一个约束public static T MaxT(T op1, T op2) where T : IComparableif (op1.CompareTo(op2) 0)return op1;return op2;在例6中,我指定的约束是,用于参数化类型的类型必须继承自(实现)Icomparable。下面的约束是可以使用的:where T : struct 类型必须是一种值类型(struct)where T : class 类型必须是一种引用类型(class)where T : new() 类型必须有一个无参数的构造器where T : class_name 类型可以是class_name或者是它的一个子类where T : interface_name 类型必须实现指定的接口你可以指定约束的组合,就象: where T : IComparable, new()。这就是说,用于参数化类型的类型必须实现Icomparable接口并且必须有一个无参构造器。9. 继承与泛型一个使用参数化类型的泛型类,象MyClass1T,称作开放结构的泛型。一个不使用参数化类型的泛型类,象MyClass1int,称作封闭结构的泛型。你可以从一个封闭结构的泛型进行派生;也就是说,你可以从另外一个称为MyClass1的类派生一个称为MyClass2的类,就象:public class MyClass2T : MyClass1int你也可以从一个开放结构的泛型进行派生,如果类型被参数化的话,如:public class MyClass2T : MyClass2T是有效的,但是public class MyClass2T : MyClass2Y是无效的,这里Y是一个被参数化的类型。非泛型类可以从一个封闭结构的泛型类进行派生,但是不能从一个开放结构的泛型类派生。即:public class MyClass : MyClass1int是有效的, 但是public class MyClass : MyClass1T是无效的。10. 泛型和可代替性当我们使用泛型时,要小心可代替性的情况。如果B继承自A,那么在使用对象A的地方,可能都会用到对象B。假定我们有一篮子水果(a Basket of Fruits (BasketFruit),而且有继承自Fruit的Apple和Banana(皆为Fruit的种类)。一篮子苹果-Basket of Apples (Basketapple)可以继承自Basket of Fruits (BasketFruit)?答案是否定的,如果我们考虑一下可代替性的话。为什么?请考虑一个a Basket of Fruits可以工作的方法:public void Package(BasketFruit aBasket)aBasket.Add(new Apple();aBasket.Add(new Banana();如果发送一个BasketFruit的实例给这个方法,这个方法将添加一个Apple对象和一个Banana对象。然而,发送一个BasketApple的实例给这个方法时,会是什么情形呢?你看,这里充满技巧。这解释了为什么下列代码:BasketApple anAppleBasket = new BasketApple();Package(anAppleBasket);会产生错误:Error 2 Argument 1: cannot convert from TestApp.Baskettestapp.apple to TestApp.Baskettestapp.fruit编译器通过确保我们不会随意地传递一个集合的派生类(此时需要一个集合的基类),保护了我们的代码。这不是很好吗?这在上面的例中在成功的,但也存在特殊情形:有时我们确实想传递一个集合的派生类,此时需要一个集合的基类。例如,考虑一下Animal(如Monkey),它有一个把BasketFruit作参数的方法Eat,如下所示:public void Eat(BasketFruit fruits)foreach (Fruit aFruit in fruits)/将吃水果的代码现在,你可以调用:BasketFruit fruitsBasket = new BasketFruit(); /添加到Basket对象中的对象FruitanAnimal.Eat(fruitsBasket);如果你有一篮子(a Basket of)Banana-一BasketBanana,情况会是如何呢?把一篮子(a Basket of)Banana-一BasketBanana发送给Eat方法有意义吗?在这种情形下,会成功吗?真是这样的话,编译器会给出错误信息:BasketBanana bananaBasket = new BasketBanana();/anAnimal.Eat(bananaBasket);编译器在此保护了我们的代码。我们怎样才能要求编译器允许这种特殊情形呢?约束机制再一次帮助了我们:public void Eatt(Baskett fruits) where T : Fruitforeach (Fruit aFruit in fruits)/将吃水果的代码在建立方法Eat()的过程中,我要求编译器允许一篮子(a Basket of)任何类型T,这里T是Fruit类型或任何继承自Fruit的类。11. 泛型和代理代理也可以是泛型化的。这样就带来了巨大的灵活性。 假定我们对写一个框架程序很感兴趣。我们需要提供一种机制给事件源以使之可以与对该事件感兴趣的对象进行通讯。我们的框架可能无法控制事件是什么。你可能在处理某种股票价格变化(double price),而我可能在处理水壶中的温度变化(temperature value),这里Temperature可以是一种具有值、单位、门槛值等信息的对象。那么,怎样为这些事件定义一接口呢?让我们通过pre-generic代理技术细致地分析一下如何实现这些:public delegate void NotifyDelegate(Object info);public interface ISourceevent NotifyDelegate NotifyActivity;我们让NotifyDelegate接受一个对象。这是我们过去采取的最好措施,因为Object可以用来代表不同类型,如double,Temperature,等等-尽管Object含有因值类型而产生的装箱的开销。ISource是一个各种不同的源都会支持的接口。这里的框架展露了NotifyDelegate代理和ISource接口。让我们看两个不同的源码:public class StockPriceSource : ISourcepublic event NotifyDelegate NotifyActivity;/public class BoilerSource : ISourcepublic event NotifyDelegate NotifyActivity;/如果我们各有一个上面每个类的对象,我们将为事件注册一个处理器,如下所示:StockPriceSource stockSource = new StockPriceSource();stockSource.NotifyActivity += new NotifyDelegate(stockSource_NotifyActivity);/这里不必要出现在同一个程序中BoilerSource boilerSource = new BoilerSource();boilerSource.NotifyActivity += new NotifyDelegate(boilerSource_NotifyActivity);在代理处理器方法中,我们要做下面一些事情: 对于股票事件处理器,我们有:void stockSource_NotifyActivity(object info)double price = (double)info; /在使用前downcast需要的类型温度事件的处理器看上去会是:void boilerSource_NotifyActivity(object info)Temperature value = info as Temperature; /在使用前downcast需要的类型上面的代码并不直观,且因使用downcast而有些凌乱。借助于泛型,代码将变得更易读且更容易使用。让我们看一下泛型的工作原理:下面是代理和接口:public delegate void NotifyDelegatet(T info);public interface ISourcetevent NotifyDelegatet NotifyActivity;我们已经参数化了代理和接口。现在的接口的实现中应该能确定这是一种什么类型。Stock的源代码看上去象这样:public class StockPriceSource : ISourcedoublepublic event NotifyDelegatedouble NotifyActivity;/而Boiler的源代码看上去象这样:public class BoilerSource : ISourcetemperaturepublic event NotifyDelegatetemperature NotifyActivity;/如果我们各有一个上面每种类的对象,我们将象下面这样来为事件注册一处理器: StockPriceSource stockSource = new StockPriceSource();stockSource.NotifyActivity += new NotifyDelegatedouble(stockSource_NotifyActivity);/这里不必要出现在同一个程序中BoilerSource boilerSource = new BoilerSource();boilerSource.NotifyActivity += new NotifyDelegatetemperature(boilerSource_NotifyActivity);现在,股票价格的事件处理器会是:void stockSource_NotifyActivity(double info) / 温度的事件处理器是: void boilerSource_NotifyActivity(Temperature info) / 这里的代码没有作downcast并且使用的类型是很清楚的。12. 泛型与反射既然泛型是在CLR级上得到支持的,你可以使用反射API来取得关于泛型的信息。如果你是编程的新手,可能有一件事让你疑惑:你必须记住既有你写的泛型类也有在运行时从该泛型类创建的类型。因此,当使用反射API时,你需要另外记住你在使用哪一种类型。我将在例7说明这一点:例7.在泛型上的反射public class MyClasst class Programstatic void Main(string args)MyClassint obj1 = new MyClassint();MyClassdouble obj2 = new MyClassdouble();Type type1 = obj1.GetType();Type type2 = obj2.GetType();Console.WriteLine(obj1s Type);Console.WriteLine(type1.FullName);Console.WriteLine(type1.GetGenericTypeDefinition().FullName);Console.WriteLine(obj2s Type);Console.WriteLine(type2.FullName);Console.WriteLine(type2.GetGenericTypeDefinition().FullName);在本例中,有一个MyClassint的实例,程序中要查询该实例的类名。然后我查询这种类型的GenericTypeDefinition()。GenericTypeDefinition()会返回MyClassT的类型元数据。你可以调用IsGenericTypeDefinition来查询是否这是一个泛型类型(象MyClassT)或者是否已指定它的类型参数(象MyClassint)。同样地,我查询MyClassdouble的实例的元数据。上面的程序输出如下:obj1s TypeTestApp.MyClass1System.Int32, mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089TestApp.MyClass1obj2s TypeTestApp.MyClass1System.Double, mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089TestApp.MyClass1可以看到,MyClassint和MyClassdouble是属于mscorlib配件集的类(动态创建的),而类MyClasst属于我自建的配件集。13. 泛型的局限性至此,我们已了解了泛型的强大威力。是否其也有不足呢?我发现了一处。我希望微软能够明确指出泛型存在的这一局制性。在表达约束的时候,我们能指定参数类型必须继承自一个类。然而,指定参数必须是某种类的基类型该如何呢?为什么要那样做呢?在例4中,我展示了一个Copy()方法,它能够把一个源List的内容复制到一个目标list中去。我可以象如下方式使用它:ListApple appleList1 = new ListApple();ListApple appleList2 = new ListApple();Copy(appleList1, appleList2);然而,如果我想要把apple对象从一个列表复制到另一个Fruit列表(Apple继承自Fruit),情况会如何呢?当然,一个Fruit列表可以容纳Apple对象。所以我要这样编写代码:ListApple appleList1 = new ListApple();ListFruit fruitsList2 = new ListFruit();Copy(appleList1, fruitsList2);这不会成功编译。你将得到一个错误:Error 1 The type arguments for method TestApp.Program.Copy

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论