深入解析C#中的泛型类与泛型接口
泛型类
泛型类封装不是特定于具体数据类型的操作。泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等。像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。
对于大多数需要集合类的方案,推荐的方法是使用.NETFramework类库中所提供的类。
- 一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时,需要特别注意以下事项:
- 将哪些类型通用化为类型参数。
- 通常,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。但是,太多的通用化会使其他开发人员难以阅读或理解代码。
- 如果存在约束,应对类型参数应用什么约束。
- 一条有用的规则是,应用尽可能最多的约束,但仍使您能够处理必须处理的类型。例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。这可以防止您的类被意外地用于值类型,并允许您对T使用as运算符以及检查空值。
- 是否将泛型行为分解为基类和子类。
- 由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。请参见本主题后面有关从泛型基类继承的规则。
- 是否实现一个或多个泛型接口。
例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能必须实现一个接口,如IComparable<T>,其中T是您的类的类型。
类型参数和约束的规则对于泛型类行为有几方面的含义,特别是关于继承和成员可访问性。您应当先理解一些术语,然后再继续进行。对于泛型类Node<T>,客户端代码可通过指定类型参数来引用该类,以便创建封闭式构造类型(Node<int>)。或者可以让类型参数处于未指定状态(例如在指定泛型基类时)以创建开放式构造类型(Node<T>)。泛型类可以从具体的、封闭式构造或开放式构造基类继承:
classBaseNode{} classBaseNodeGeneric<T>{} //concretetype classNodeConcrete<T>:BaseNode{} //closedconstructedtype classNodeClosed<T>:BaseNodeGeneric<int>{} //openconstructedtype classNodeOpen<T>:BaseNodeGeneric<T>{}
非泛型类(换句话说,即具体类)可以从封闭式构造基类继承,但无法从开放式构造类或类型参数继承,因为在运行时客户端代码无法提供实例化基类所需的类型参数。
//Noerror classNode1:BaseNodeGeneric<int>{} //Generatesanerror //classNode2:BaseNodeGeneric<T>{} //Generatesanerror //classNode3:T{}
从开放式构造类型继承的泛型类必须为任何未被继承类共享的基类类型参数提供类型变量,如以下代码所示:
classBaseNodeMultiple<T,U>{} //Noerror classNode4<T>:BaseNodeMultiple<T,int>{} //Noerror classNode5<T,U>:BaseNodeMultiple<T,U>{} //Generatesanerror //classNode6<T>:BaseNodeMultiple<T,U>{}
从开放式构造类型继承的泛型类必须指定约束,这些约束是基类型约束的超集或暗示基类型约束:
classNodeItem<T>whereT:System.IComparable<T>,new(){} classSpecialNodeItem<T>:NodeItem<T>whereT:System.IComparable<T>,new(){}
泛型类型可以使用多个类型参数和约束,如下所示:
classSuperKeyType<K,V,U> whereU:System.IComparable<U> whereV:new() {}
开放式构造类型和封闭式构造类型可以用作方法参数:
voidSwap<T>(List<T>list1,List<T>list2) { //codetoswapitems } voidSwap(List<int>list1,List<int>list2) { //codetoswapitems }
如果某个泛型类实现了接口,则可以将该类的所有实例强制转换为该接口。
泛型类是不变的。也就是说,如果输入参数指定List<BaseClass>,则当您尝试提供List<DerivedClass>时,将会发生编译时错误。
泛型接口
为泛型集合类或表示集合中项的泛型类定义接口通常很有用。对于泛型类,使用泛型接口十分可取,例如使用IComparable<T>而不使用IComparable,这样可以避免值类型的装箱和取消装箱操作。.NETFramework类库定义了若干泛型接口,以用于System.Collections.Generic命名空间中的集合类。
将接口指定为类型参数的约束时,只能使用实现此接口的类型。下面的代码示例显示从SortedList<T>类派生的GenericList<T>类。
SortedList<T>添加约束whereT:IComparable<T>。这将使SortedList<T>中的BubbleSort方法能够对列表元素使用泛型CompareTo方法。在此示例中,列表元素为简单类,即实现Person的IComparable<Person>。
//TypeparameterTinanglebrackets. publicclassGenericList<T>:System.Collections.Generic.IEnumerable<T> { protectedNodehead; protectedNodecurrent=null; //NestedclassisalsogenericonT protectedclassNode { publicNodenext; privateTdata;//Tasprivatememberdatatype publicNode(Tt)//Tusedinnon-genericconstructor { next=null; data=t; } publicNodeNext { get{returnnext;} set{next=value;} } publicTData//Tasreturntypeofproperty { get{returndata;} set{data=value;} } } publicGenericList()//constructor { head=null; } publicvoidAddHead(Tt)//Tasmethodparametertype { Noden=newNode(t); n.Next=head; head=n; } //Implementationoftheiterator publicSystem.Collections.Generic.IEnumerator<T>GetEnumerator() { Nodecurrent=head; while(current!=null) { yieldreturncurrent.Data; current=current.Next; } } //IEnumerable<T>inheritsfromIEnumerable,thereforethisclass //mustimplementboththegenericandnon-genericversionsof //GetEnumerator.Inmostcases,thenon-genericmethodcan //simplycallthegenericmethod. System.Collections.IEnumeratorSystem.Collections.IEnumerable.GetEnumerator() { returnGetEnumerator(); } } publicclassSortedList<T>:GenericList<T>whereT:System.IComparable<T> { //Asimple,unoptimizedsortalgorithmthat //orderslistelementsfromlowesttohighest: publicvoidBubbleSort() { if(null==head||null==head.Next) { return; } boolswapped; do { Nodeprevious=null; Nodecurrent=head; swapped=false; while(current.next!=null) { //Becauseweneedtocallthismethod,theSortedList //classisconstrainedonIEnumerable<T> if(current.Data.CompareTo(current.next.Data)>0) { Nodetmp=current.next; current.next=current.next.next; tmp.next=current; if(previous==null) { head=tmp; } else { previous.next=tmp; } previous=tmp; swapped=true; } else { previous=current; current=current.next; } } }while(swapped); } } //AsimpleclassthatimplementsIComparable<T>usingitselfasthe //typeargument.Thisisacommondesignpatterninobjectsthat //arestoredingenericlists. publicclassPerson:System.IComparable<Person> { stringname; intage; publicPerson(strings,inti) { name=s; age=i; } //Thiswillcauselistelementstobesortedonagevalues. publicintCompareTo(Personp) { returnage-p.age; } publicoverridestringToString() { returnname+":"+age; } //MustimplementEquals. publicboolEquals(Personp) { return(this.age==p.age); } } classProgram { staticvoidMain() { //DeclareandinstantiateanewgenericSortedListclass. //Personisthetypeargument. SortedList<Person>list=newSortedList<Person>(); //CreatenameandagevaluestoinitializePersonobjects. string[]names=newstring[] { "Franscoise", "Bill", "Li", "Sandra", "Gunnar", "Alok", "Hiroyuki", "Maria", "Alessandro", "Raul" }; int[]ages=newint[]{45,19,28,23,18,9,108,72,30,35}; //Populatethelist. for(intx=0;x<10;x++) { list.AddHead(newPerson(names[x],ages[x])); } //Printoutunsortedlist. foreach(Personpinlist) { System.Console.WriteLine(p.ToString()); } System.Console.WriteLine("Donewithunsortedlist"); //Sortthelist. list.BubbleSort(); //Printoutsortedlist. foreach(Personpinlist) { System.Console.WriteLine(p.ToString()); } System.Console.WriteLine("Donewithsortedlist"); } }
可将多重接口指定为单个类型上的约束,如下所示:
classStack<T>whereT:System.IComparable<T>,IEnumerable<T> { }
一个接口可定义多个类型参数,如下所示:
interfaceIDictionary<K,V> { }
适用于类的继承规则同样适用于接口:
interfaceIMonth<T>{} interfaceIJanuary:IMonth<int>{}//Noerror interfaceIFebruary<T>:IMonth<int>{}//Noerror interfaceIMarch<T>:IMonth<T>{}//Noerror //interfaceIApril<T>:IMonth<T,U>{}//Error
如果泛型接口为逆变的,即仅使用其类型参数作为返回值,则此泛型接口可以从非泛型接口继承。在.NETFramework类库中,IEnumerable<T>从IEnumerable继承,因为IEnumerable<T>只在GetEnumerator的返回值和Current属性getter中使用T。
具体类可以实现已关闭的构造接口,如下所示:
interfaceIBaseInterface<T>{} classSampleClass:IBaseInterface<string>{}
只要类参数列表提供了接口必需的所有参数,泛型类便可以实现泛型接口或已关闭的构造接口,如下所示:
interfaceIBaseInterface1<T>{} interfaceIBaseInterface2<T,U>{} classSampleClass1<T>:IBaseInterface1<T>{}//Noerror classSampleClass2<T>:IBaseInterface2<T,string>{}//Noerror