C#泛型编程指导原则.docx
- 文档编号:24164191
- 上传时间:2023-05-24
- 格式:DOCX
- 页数:15
- 大小:21.89KB
C#泛型编程指导原则.docx
《C#泛型编程指导原则.docx》由会员分享,可在线阅读,更多相关《C#泛型编程指导原则.docx(15页珍藏版)》请在冰豆网上搜索。
C#泛型编程指导原则
Item1:
Use GenericCollections使用泛型集合
数据集合是泛型最典型的应用之一。
在你现存的代码里,可能已经遍布了ArrayList和HashTable。
在使用泛型之前,你可能会在将要编写的代码中大量使用System.Collection这个数据结构。
然而,自从有了泛型,真的是没有任何理由再继续使用这个命名空间中的集合了。
如果说仅仅有那么一个领域,泛型可以带来毫无疑问的价值的话,那么这个领域就是集合。
如果没有泛型,非泛型集合的生产者和消费者将被迫把被包含的类型声明为Objet。
当然了,这样做的话,你的代码将会有很多强制转换和一般的类型转换(将每个Object对象转换为其真实的类型),这将使代码变得很凌乱,同时也意味着对于值类型需要将其进行装箱才能表示为Object类型。
即使在你努力的限制非泛型集合的影响的时候,一般你都会被迫进行对特定类型的包装,从而使类膨胀。
出于这些原因和其他一些在使用中的体会,可以很明确的得知:
没有任何理由倾向于这些老式的、非泛型的集合了。
事实上,我想这样告诉大家:
泛型集合是泛型最引人入胜的用处。
如果你并没有被使用泛型集合的价值所说服的话,那么泛型所带来的任何其他好处也不能说服你。
尽管我认为使用泛型的观点是不得不接受的,但是并不是每个解决方案都具有用泛型集合完全取代与其相对应的非泛型集合的荣耀。
如果你公开了一个API,并且有客户绑定了非泛型的API,你就需要考虑如何将API转换成泛型的。
在这种情况下,在具体实现中,加入泛型集合到API中依然是值得的。
Item2:
ReplaceObjectswithTypeParameters使用类型参数取代Object类型
在使用泛型之前,程序员一般都依赖于Object类型来获得通用性。
如果有这样一个类或者方法:
共性的功能,会被应用到不同的类型上,你将由很少的选择来处理。
如果没有一个共同的基类或者接口,仅有的方法就是使用通用的类型Object。
如:
publicobjectSendMsg(objectsender,objectparam){}
当然,这样做是以牺牲类型安全为代价来换取的通用性。
泛型为解决这种类型安全问题引入了一个完美的解决方法,通过使用泛型,可以在类型安全和通用性之间得到平衡。
上面的方法可以改成如下的模式:
publicKSendMsg(Isender,Jparam){}
这里来自经验的最基本的规则就是:
在代码中应该尽量不使用Object类型,当遇到Object类型时,都应该先问问自己:
泛型能否用在这里来减少对Object的依赖。
Item3:
ReplaceSystem.TypewithTypeParameters用类型参数取代System.Type
在一些情况下,你可能在方法的签名中使用过System.Type引用,以允许在基于支持的特定类型的情况下,改变方法的行为。
如:
publicobjectFindPerson(TypepersonType,intid){}
如果使用泛型,可以成为:
publicTFindPerson
这样一来,接口将会更清晰,也可以减少方法实现的复杂性。
同时意味着该方法的用户不必强迫将该方法的返回值转换成指定的类型。
Item4:
UseTypeParametersforRefTypes对ref类型使用类型参数
Item2谈到了用类型参数替换Object类型的一般策略,该规则有一个变种,针对该变种引入一个新的条款还是很有必要的。
本条款的焦点在于Object类型的数据作为了引用参数。
如:
publicvoidSort(refobjectparam1,refobjectparam2){}
一般的客户是这样使用该方法的;
publicvoidprocessItems()
{
Personperson1=newPerson(424);
Personperson2=newPerson(190);
Sort(refperson1,refperson2);
}
从表面上来看这样很好。
Person将被强制转换为Object类型,并传递给该方法。
如果方法没有将参数指定为ref,那么该方法也就没有什么问题了。
然而使用了ref关键字后,便一起将要求传入的参数类型与方法签名中指定的参数类型精确匹配。
显然,这里的Person与Object不是精确匹配。
因此,这样的话,可以通过将Person转换为Object来解决,但是,这样完全没有必要。
通过泛型方法可以这样解决:
publicvoidSort
使用了泛型后,方法的类型就精确匹配了。
在很多方面,该条款看起来是条款2的重复。
然而,使用ref产生的复杂关系使得该条款有单独存在的必要。
类型引起变化的类型泛型化
如果你通读以下自己代码中现存的类、方法、接口、委托,你会发现,自己一般会使用一个类型所包含/管理的类型来定义它。
在这些情况下,需要考虑是否要应用泛型,从而可以使一个单独的实现就可以为多种数据类型提供服务。
在这样的场景下应用泛型可以产生一系列的积极作用,包括减少代码的大小,提高类型安全性等等。
--[if!
supportLists]-->一.
--[endif]-->消除冗赘的数据容器:
迄今为止,数据容器是用来进行泛型重构的最通用、最直接的领域之一。
大多数的解决方案至少有一或两个这样的例子:
为了向ArrayList的安全性妥协,你创建了自己的类型安全的包装类。
如:
publicclassPersonCollection
{
privateArrayList_persons;
publicPersonCollection()
{
_persons=newArrayList();
}
publicvoidAdd(Personperson)
{
_persons.Add(person);
}
publicPersonthis[intindex]
{
get{return(Person)_persons[index];}
}
}
publicclassOrderCollection
{
privateArrayList_orders;
publicOrderCollection()
{
_orders=newArrayList();
}
publicvoidAdd(Orderorder)
{
_orders.Add(order);
}
publicOrderthis[intindex]
{
get{return(Order)_orders[index];}
}
}
这两个类封装了一个ArrayList,向客户提供了一个类型安全的接口,他们是进行泛型重构的极好候选者。
这两个类,除了管理的数据类型不同外,就没有什么不同的了。
通过使用泛型集合Collection
具体我们只要使用Collection
如果你的候选类提供的功能没有被Collection
--[if!
supportLists]-->二.
--[endif]-->定义候选方法:
为了进行泛型重构而寻找候选方法,是很细小琐碎而又缺少精确性的科学。
最常见的例子就是:
一些方法在整个对象上执行了相当基本的操作,没有调用任何特别的方法。
如:
publicstaticvoidSwap(refStringval1,refStringval2)
{
StringtmpObj=val2;
val2=val1;
val1=tmpObj;
}
publicstaticvoidSwap(refDoubleval1,refDoubleval2)
{
DoubletmpObj=val2;
val2=val1;
val1=tmpObj;
}
如果有多个类型的数据要进行交换时,为了获得类型安全、避免对值类型进行装箱,需要提供一系列的overloaded方法。
如果使用Object类型作为参数的话,将引起一系列的问题,同时这样做也违背了条款2。
最好的方法就是使用泛型。
如下所示:
publicstaticvoidSwap
{
TtmpObj=val2;
val2=val1;
val1=tmpObj;
}
--[if!
supportLists]-->三.
--[endif]-->用一个泛型委托替换多个委托
泛型委托的引入在根本上改变了应该在何时、如何创建自己的委托。
委托代表了泛型最根本、最自然的应用中的一种。
如,非泛型委托是这样的:
publicdelegatevoidMyDel1(intx,stringy);
publicdelegatevoidMyDel2(intx,doubley);
publicdelegatevoidMyDel3(intx,longy);
publicdelegatevoidMyDel4(intx,stringy,doublez);
publicdelegatevoidMyDel5(intx,doubley,doublez);
通过使用委托,可以使其大大简化:
publicdelegatevoidMyDel
publicdelegatevoidMyDel
Item6:
UseExpressive,ConsistentTypeParameterNames使用富有表现力的、前后一致的类型参数名称
对于该问题,有两大基本阵营。
一方认为“单个字母”的类型参数名称更好,因为它减少了泛型声明的签名的大小,这是被大多数C++模板库所使用的模式。
另一方认为一个字母过于简短,不足以表达类型参数的本质意义,他们希望用长一点的、更具有表述性的名字。
用中国话最好来解释了:
具体问题具体分析,在两种方法之间折中处理。
下面的例子极好的表现了这一点:
publicclassDictionary
publicclassDictionary
Item7:
UseAliasingforComplexorFrequentlyUsedTypes对复杂的或者频繁使用的类型,使用别名
简单的不需要解释☺
看例子☺
publicvoidProcessItem(MyType1
{
MyType1
if(status==1)
{
MyType1
}
Else
{
Nullable
newNullable
}
}
使用别名后可以简化为:
usingMType=MyType1
publicvoidProcessItem(MTypevalue,intstatus)
{
MTypex=newMType();
if(status==1)
{
MTypey=value;
}
else
{
Nullable
}
}
Item8:
Don’tUseConstructedTypesasTypeArguments不要把构造类型作为类型参数
虽然你拥抱了泛型的光彩,依然需要确保不要走极端。
因为有可能在选择使用了泛型后,处理过程反而没有原本的优雅。
如:
publicclassMyComplexType
publicclassMyType2
publicclassMyType3
publicclassTestClass
{
publicvoidfoo()
{
MyComplexType
newMyComplexType
}
}
从该例子可以看出,这样做极大的影响了程序的可读性。
Item9:
Don’tUseTooManyTypeParameters不要使用太多的类型参数
一般来说,类型参数不要超过2个。
因为使用的类型参数越多,就越难使用、维护和理解。
Item10:
PreferTypeInferencewithGenericMethods优先使用泛型方法的类型推测
泛型方法的最帅的特性就是推测参数类型的能力,该特性可以消除为每个对泛型方法的调用者显式提供参数类型的需要。
这对整个代码的可维护性、可读性都有着显著的影响。
如:
publicclassTypeInference
{
publicvoidMyInferenceMethod(Iparam1,Jparam2){}
publicvoidMakeInferenceCall()
{
MyInferenceMethod(“TestVal”,122);
MyInferenceMethod(122,“TestVal”);
MyInferenceMethod(newOrder(),833.22);
}
}
上面使用了泛型方法的类型推测特性。
这使得对泛型方法的调用变得透明了。
这些调用看起来和非泛型方法的调用没有区别,看起来就是是overloaded一样。
Item11:
Don’tMixGenericandNon-GenericStaticMethods不要混淆泛型静态方法与非泛型静态方法
如果在泛型类中同时又有泛型静态方法与非泛型静态方法,就很容易造成混乱。
如有这样一个类:
publicclassTestClass
{
publicstaticvoidFoo(){}
publicstaticvoidFoo
}
客户这样调用的话:
TestClass
TestClass.Foo
将产生很大的混乱。
出现这种情况的处理方法很简单:
修改方法名称。
Item12:
CustomCollectionsShouldExtendCollection
在一些时候,你很希望引入自己定义的泛型集合。
典型来讲,自定义的泛型集合都应该由现有的来Collection
这样就可以继承它的行为,并且可以根据自己特定的需要来补充或者修改其功能。
在这些情况下,你可能尝试使自定义泛型集合扩展自List
List
然而,为了使其最优,该类阻止客户重写(Overrid)或者改变它的行为。
假设,你可以修改List类,使其可以在一个Item被添加或者被移除的时候记录一些额外的数据。
但是List
因此,虽然List
相反,Collection
虽然他并没有List
Item13:
UsetheLeastSpecializedInterfaceinYourAPIs在自己的API中使用最小的限定接口
在System.Collections.Generic命名空间中所包含的集合,实现了一系列不同的接口,这些接口为管理集合、与集合交互提供了不同层次的支持。
在自己的API中,应该根据需要选择最合适的接口。
一条从经验中得来的规则就是,在自己的API中使用最小的限定接口。
如,如果希望在集合中依次迭代集合中的Item,仅仅需要实现IEnumerable
Item14:
Enable“foreach”IterationwithIEnumerable
System.Collections.Generic命名空间包含了IEnumerable
该接口为迭代集合中的元素提供了一个标准的机制。
在框架中,它的角色比其他基于集合的接口更显著。
foreach提供了一个简洁的、可读性强的途径来访问集合中的所有元素(Item)。
因此,在自己的泛型中最好实现对foreach的支持。
Item15:
SelecttheLeastRestrictiveConstraints选择最少限制的约束
在给类型参数选择约束条件的时候,最好只加入最少的限制,不要加入不必要的附加限制。
下面是一个约束过头的例子。
publicinterfaceIPerson
{
voidValidate();
}
publicinterfaceICustomer:
IPerson{}
publicinterfaceIEmployee:
IPerson{}
publicclassTestClass
ICustomer
{
publicTestClass(Tval)
{
val.Validate();
}
}
这个例子中有一个接口的继承。
对TestClass
因为这里需要的约束是Validate(),而它是IPerson的一部分,所以你应该使用IPerson作为约束条件。
Item16:
Don’tImposeHiddenConstraints不要强加隐式的约束条件
即使你的类型没有在声明的时候包含任何约束,这并不意味着你的类型不能在实现中强加隐式的约束。
可以想象,通过强制转换或者调用GetType()方法,可以在泛型类型中生成建立于类型参数的代码,在这样的情况下,你仍然对类型参数强加了非直接的约束—仅仅是没有显式的声明而已。
这种隐式的约束当然是约束,同时是糟糕的主意。
Item17:
AvoidMultipleConstraintAmbiguity避免多重约束中的含糊
当使用约束的时候,可以选择对任何一个类型参数使用多重约束。
事实上,你可以将单独的类约束与多重接口约束联合使用。
当你开始混合并匹配多重约束的时候,可能引入含糊不清的约束。
如:
publicinterfaceI
{
voidFoo1();
voidFoo3();
}
publicclassC
{
publicvoidFoo1(){}
}
publicclassTestClass
C,I{}
这里接口I和类C中的Foo1产生了混淆。
Item18:
ProvideParameterlessConstructors提供无参构造器
无论何时,当你引入自定义类型的时候,都需要考虑当他们被用作类型参数的时候会产生什么样的行为。
显然,你选择实现的接口会在该类型如何被约束方面起关键性的作用。
至少,准备用为类型参数的每个类型都应该包含对参数构造的支持。
通过支持这个约束,使得你的类型可以被任何包含构造约束的泛型类型所支持。
通过支持无参数的构造器来增加你的接口的价值的实例足够多,不过是对于泛型还是非泛型解决方案。
如果你曾经和工厂模式的任何一种变形打过交道,你可能已经提供过无参构造器。
有了泛型,该条款可以增加价值的场景的列表只会变得更长。
例如:
publicIEnumerable
new(){}
Item19:
UseStaticDataMemberswithCaution小心使用静态数据成员
对于非泛型类来说,静态数据成员被所有该类的对象所共享。
然而,对于泛型类型,静态数据成员是被所有拥有相同类型参数的类型所共享。
如:
publicclassStaticData
{
privatestaticint_staticData=0;
publicvoidIncrementCount()
{
_staticData++;
}
}
通过下面的方法来测试:
publicvoidTestStaticData()
{
StaticData
instance1.IncrementCount(); //1
StaticData
instance2.IncrementCount(); //1
StaticData
instance3.IncrementCount(); //2
}
请注意注释中标注的数字。
instance1和instance3共享同一个静态数据成员_staticData。
Item20:
UseInterfacesinLieuofClasses使用接口取代类
原理不讲了,所有OOP人员都应该明白的。
Item21:
UseComparer
Item22:
UseNullable
Item23:
UseEventHandler
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C# 编程 指导 原则