方法、属性、事件、构造函数已经字段统称为成员。
5.1 成员设计的一般规范
5.1.1 成员重载
成员重载是指在同一个类型中创建两个或两个以上的成员,这些成员有相同的名字,唯一不同的是参数的数量或参数的类型。因为只有方法、构造函数以及索引属性可以有参数,所以只有这些成员可以被重载。
要尽量用描述性的参数名来说明在较短的重载中使用的默认值。在一族对参数的数量进行重载的成员中年,较长的重载应该用参数名来说明与之对应的较短的重载所使用的默认值,这最适用于布尔型参数。
避免在重载中随意地给参数命名。如果两个重载中的某个参数表示相同的输入,那么该参数的名字应该相同。
避免使重载成员的参数顺序不一致。在所有的重载中,同名参数应该出现在相同的位置。
要把最长的重载做成虚函数(如果需要可扩展性),较短的重载应该仅仅是调用较长的重载。
不要在重载成员中使用ref或out修饰符
要允许可选参数为null,如果方法有可选的引用类型参数,那么要允许它为null。
要优先使用成员重载,而不是定义有默认参数的成员。
5.1.2 显式地实现接口成员
接口成员的显式实现使得客户代码在调用我们实现的接口成员时,必须把实例强制转换为接口类型。
public struct int32:IConvertible
{
int IConvertible.ToInt32(){}
}
int i=0;
i.ToInt32();//does not compile
((IComvertible)i).ToInt32();//works just fine
避免显示地实现接口成员,如果没有很强的理由
考虑显式地实现接口成员,如果希望接口成员只能通过该接口来调用,这主要包括哪些用于支持框架基础设施的成员,比如数据绑定或序列化。
考虑通过显式地实现接口成员来模拟variance(在被覆盖的成员中改变参数或返回值类型)
考虑在需要隐藏一个成员并增加另一个名字更合适的登记成员时,显式地实现接口成员,可以说着相当于对成员进行重命名。System.IO.FileStream显示地实现了IDisposable.DIspose,并将它重命名为FileStream.Close。
不要把接口成员的显示实现用作安全壁垒,只要把实例强制转换为接口,任何代码都可以调用此类成员。
要为显示实现的接口成员提供一个功能相同的受保护的虚成员,如果希望让派生类对该功能进行定制。
5.1.3 属性和方法之间的选择
考虑使用属性,如果该成员表示类型的逻辑attribute,Button.Color是属性,因为color是button的一个attribute.
要使用属性而不要使用方法---如果属性的值存储在内存中,而提供属性的目的仅仅是为了访问该值。
要在下列情况中使用方法而不要使用属性:
该操作比字段方法要慢一个或多个数量级。如果为了避免线程阻塞,你甚至在考虑为该操作提供一个异步的版本,那么对于一个属性来说,该操作的开销很可能太大了。
该操作每次返回的结果不同,即使传入的参数不变,Guid.NewGuid
该操作有严重的、能观察到的副作用。注意,一般来说我们并不把对内部缓存的操作看成是能观察到的副作用。
该操作返回内部状态的一个副本(不包括哪些栈上的值类型的副本)
该操作返回一个数组
5.2 属性的设计
要创建只读属性,如果不应该让调用方法改变属性的值。
不要提供只写属性,也不要让设置方法的存取范围比获得方法更广。如果不能提供属性的获取方法那么应该把该功能作为方法来实现。
要为所有的属性提供合理的默认值,这样可以确保默认值不会导致安全漏洞或效率极低的代码。
要允许用户以任何顺序来设置属性的值,即使这可能会是对象在短时间内处于无效状态
要暴露属性原来的值,如果属性的设置方法抛出异常
避免在属性的获取方法中抛出异常(这条不适用于索引器)
5.2.1 索引属性的设计
考虑通过索引器的方法让用户方法存储在内部数组中的数据
考虑为代表项集的类型提供索引器
避免有一个以上的参数的索引器
避免用System.Int32、System.Int64、System.String、System.Object.枚举或泛型参数之外的类型来做索引器的参数。
要用Item来做索引属性的名字,除非有明显更好的名字。
不要同时提供语义上等价的索引器和方法。
不要在以俄国类型中提供具有不同名字的索引器
不要是同飞默认的索引属性
5.2.2 属性改变的通知事件
考虑在高层API的属性值被修改是除非属性改变的通知事件
考虑在属性值被外界修改是触发通知事件。
5.3 构造函数的设计
有两种类型的构造函数:类型构造函数和实例构造函数。
public class Customer
{
public Customer(){}//instance constructor
static CUstomer(){}//type constructor
}
类型构造函数是静态的,CLR会在使用该类型之前运行它。实例构造函数在类型的实例创建时运行。
类型构造函数不能带任何参数,实例构造函数则可以。不带任何参数的实例构造函数通常称为默认构造函数。
考虑提供简单的构造函数,最好是默认构造函数
考虑用静态的工厂方法来代替构造函数--如果如果让想要执行的操作的语义与新实例的构造函数直接对应,或者遵循构造函数的设计规范会感到不自然。
要把构造函数的参数用作设置主要属性的便捷方法。
要用相同的名字命名构造函数参数和属性,如果构造函数用于简单地设置属性。
要在构造函数中做最少的工作,处理取得构造函数的参数之外,构造函数不应该做太多的工作。
要从实例构造函数中抛出异常,如果合适。
要在类中显式的声明公用的默认构造函数,如果这样的构造函数是必需的。
给类添加带参数的构造函数会阻止编译器生成默认的构造函数。最好的做法时始终显式的提供默认的构造函数。
避免在结构中显示的定义默认构造函数
避免在对象的构造函数内部调用虚成员,调用虚成员实际上调用的是派生类中覆盖该虚成员的最深层成员,即使最深层派生类型的构造函数尚未完成运行也是如此。
类型构造函数也称为静态构造函数,用来初始化类型。运行库会在创建类型的第一个实例或访问类型的任何静态成员之前调用静态构造函数。
要把静态构造函数声明为私有的。
不要在静态构造函数中抛出异常。
考虑以内联的形式初始化静态字段,而不是显式地定义静态构造函数,这是因为运行库能够对那些没有显式定义静态构造函数的类型进行性能分析。
5.4 事件的设计
要在事件中使用术语“raise”,而不是使用“fire”或“trigger”。
要用Systm.EventHandler<T>来定义事件处理函数,不要手工创建新的委托。
考虑用EventArgs的子类来做事件的参数,除非百分之百确信该事件不需要给事件处理方法传递任何数据,在这种情况下可以直接使用EventArgs.
要用受保护的虚函数来触发事件。这只适用于非密封类中的非静态事件,不适用于结构、密封类以及静态事件。
要让触发事件的受保护的方法带一个参数,该参数的类型为事件参数类,该参数的名字应该为e
不要在触发非静态事件时把null作为sender参数传入。
不要在触发事件时把null作为数据参数传入。
考虑触发能够被最终用户取消的事件,这只适用于前置事件。
自定义事件处理函数的设计
要把事件处理函数的返回类型定义为Void
要用Ojbect作为事件处理函数的第一个参数类型,并将其命名为sender。
要用Sysetm.EventArgs或其子类作为事件处理函数的第二个参数的类型,并将其命名为e
不要在事件处理函数中使用两个以上的参数
5.5 字段的设计
不要提供公有的活受保护的实例字段,应该提供属性来访问字段,而不应该使字段为公有的或受保护的。
要用常量字段来表示永远不会改变的常量
要用共有的静态只读字段来定义预定义的对象实例。
不要把可变类型的实例赋值给只读字段。
5.6 操作符重载
避免定义操作符重载,除非该类型看起来应该像个基本类型。
考虑在看起来应该像基本类型的类型中定义操作符重载
要为表示数值的结构定义操作符重载
不要在定义操作符重载时耍小聪明
不要提供操作符重载,除非至少有一个操作数的类型就是定义该重载的类型。
要以对称的方式来重载操作符,如果重载了operator==,那么应该同时重载operator!=。
考虑为每个重载过的操作符同对应的方法,并容易理解的名字来命名。
5.7 参数的设计
要用类层次结构汇总最接近基类的类型来作为参数的类型,同时要保证该类型能够同成员所需的功能。
不要使用保留参数,如果将来成员需要更多的参数,那么可以增加一个重载成员