首页 > 代码库 > 《.NET 设计规范》第 5 章:成员设计
《.NET 设计规范》第 5 章:成员设计
《.NET 设计规范》第 5 章:成员设计
5.1 成员设计的通用规范
要尽量用描述性的参数名来说明在较短的重载中使用的默认值。
避免在重载中随意地改变参数的名字。如果两个重载中的某个参数表示相同的输入,那么该参数的名字应该相同。
避免使重载成员的参数顺序不一致。在所有的重载中,同名参数应该出现在相同的位置。
要把最长的重载成员定义成重载成员中唯一的虚成员。
不要用 ref 或 out 修饰符来对成员进行重载。
不要定义这样的重载:位于同一个位置的参数有相似的类型但却有不同的语义。
要允许在传递参数时将可选参数设为 null。
要优先使用成员重载,而不是定义有默认参数的成员。
显式地实现接口成员。
避免显示地实现接口成员 - 如果没有很强的理由。
考虑显式地实现接口成员 - 如果希望接口成员只能通过该接口来调用。
考虑通过显式地实现接口成员的方式来模拟变体。
考虑在需要隐藏一个成员并增加另一个名字更合适的等价成员时,显式地实现接口成员。
不要把接口成员的显示实现当做安全壁垒。
要为显式实现的接口成员提供具有相同功能的受保护的虚成员 - 如果希望让派生类对该功能进行定制。
考虑使用属性 - 如果该成员表示类型的一种逻辑属性。
要使用属性而不要使用方法 - 如果属性的值储存在进程内存中,而且提供属性的目的仅仅是为了访问该值。
要在下列情况中时使用方法而不要使用属性:
该操作比字段访问要慢几个数量级;
该操作是一个转换操作,如:object.ToString() 方法;
该操作在每次调用时都返回不同的结果,即使传入的参数不变。如:Guid.NewGuid 方法在每次都返回不同的值;
该操作有严重的、显而易见的副作用;
该操作返回内部状态的一个副本(这不包括那些在栈上返回的值类型对象的副本);
该操作返回一个数组。
5.2 属性的设计
要创建只读属性 - 如果调用方不应该改变属性的值。
不要提供只写属性,也不要让 setter 的可访问性比 getter 更广。
要为所有的属性提供合理的默认值,这样可以确保默认值不会导致安全漏洞或效率低下的代码。
要允许用户以任何顺序来设属性的值,即使这可能会使对象在短时间内处于无效状态。
要保留属性原来的值,如果属性的 setter 抛出异常。
避免在属性的 getter 中抛出异常。
考虑通过索引器的方式让用户访问存储在呢不数组中的数据。
考虑为代表元素集合的类型提供索引器。
避免使用有一个以上参数的索引属性。
避免用 System.Int32、System.Int64、System.String、System.Object、枚举或泛型参数之外的类型来作索引器的参数。
要将 Item 名称用于索引属性,除非有明显更好的名字(例如 System.String 的 Chars 属性)。
不要同时提供语义上等价的索引器和方法。
不要在一个类型中提供具有不同名字的索引器。
不要使用非默认的索引属性。
考虑在高层 API 的属性值被修改时触发属性改变的通知事件。
考虑在属性值被外界修改时触发通知事件。
5.3 构造函数的设计
考虑提供简单的构造函数,最好是默认构造函数。
考虑用静态工厂方法来代替构造函数 - 如果无法让想要执行的操作的语义与新实例的构造函数直接对应,或者遵循构造函数的设计规范会让人感觉不合理。
要把构造函数的参数列表当做设置主要属性的快捷方法。
要用相同的名字来命名构造函数的参数和属性 - 如果定义该构造函数参数的目的就是为了设置对应的属性。
要在构造函数中做最少的工作。
要在适当的时候从实例构造函数中抛出异常。
要在类中显式地声明公有的默认构造函数 - 如果这样的构造函数是必需的。
避免在结构中显式地定义默认构造函数。
避免在对象的构造函数内部调用虚成员。
要把静态构造函数声明为私有。
不要从静态构造函数中抛出异常。
考虑以内联的形式来初始化静态字段,而不要显式地定义静态构造函数,这是因为运行库能够对那些没有显示定义静态构造函数的类型进行性能优化。
5.4 事件的设计
要在事件中使用术语“raise”,而不要使用“fire”或“trigger”。
要用 System.EventHandler<T> 来定义事件处理函数,而不是手工创建新的委托来定义事件处理函数。
考虑用 EventArgs 的子类来做事件的参数,除非百分之百确信该事件不需要给事件处理方法传递任何数据,在这种情况下可以直接使用 EeventArgs。
要用受保护的虚方法来触发事件。这只适用于非密封类中的非静态事件,不适用于结构、密封类以及静态事件。
要让触发事件的受保护的方法带一个参数,该参数的类型为事件参数类,该参数的名字应该为 e。
不要在触发非静态事件时把 null 作为 sender 参数传入。
要在触发静态事件时把 null 作为 sender 参数传入。
不要在触发事件时把 null 作为数据参数传入。
考虑触发能够被最终用户取消的事件,这适用于前置事件。
要把事件处理函数的返回类型定义为 void。
要用 object 作为事件处理函数的第一个参数的类型,并将其命名为 sender。
要用 System.EventArgs 或其子类作为事件处理函数的第二个参数的类型,并将其命名为 e。
不要在事件处理函数中使用两个以上的参数。
5.5 字段的设计
不要提供公有的或受保护的实例字段。
要用常量字段来表示永远不会改变的常量。
要用公有的静态只读字段来定义预定义的对象实例。
不要把可变类型的实例赋值给只读字段。
5.6 扩展方法
避免草率地定义扩展方法,尤其是为别人的类型定义扩展方法。
考虑在下面的场景中使用扩展方法。
为一个接口的所有实现提供相关的辅助方法,而且这些功能可以通过核心接口来表达。
如果增加一个实例方法会引入对其它类型的依赖关系,而依赖关系会破坏依赖关系的管理规则,那么应该使用扩展方法。
避免为 System.Object 定义扩展方法。
不要把扩展方法和被扩展的类型放在同一个命名空间中 - 除非是为了把方法增加到接口中,或是为了对依赖关系进行管理。
避免在定义两个扩展方法时使用相同的签名,即使它们位于不同的命名空间。
考虑把扩展方法和被扩展的类型放在同一个命名空间 - 如果被扩展的类型是接口,而且该扩展方法的设计目的就是要用于大多数的情况甚至是所有的情况。
不要把实现某个特性的扩展方法放在一个通常与其他特性相关联的命名空间中。相反,该特性属于哪个命名空间,就应该把对应的扩展方法放在那里。
避免使用太宽泛的名字(例如“Extensions”)来给扩展方法专用的命名空间命名,要使用更具描述性的名字(比如“Routing”)。
5.7 操作符重载
避免定义操作符重载,除非该类型让人感觉像个基本(内置)类型。
考虑在让人感觉应该想基本类型的类型中定义操作符重载。
要为表示数值的结构(比如 System.Decimal)定义操作符重载。
不要在定义操作符重载时耍小聪明。
不要提供操作符重载,除非至少有一个操作数的类型是定义该操作符重载的类型。
要以对称的方式来重载操作符。
考虑为每个重载过的操作符提供对应的方法,并用容易理解的名字来命名。
不要提供类型转换操作符 - 如果没有明确的用户需求。
不要在定义类型转换操作符时超越类型所在的领域。
不要提供隐式类型转换操作符 - 如果这样的类型转换可能会丢失精度。
不要从隐式的强制类型转换操作符中抛出异常。
要抛出 System.InvalidCastException - 如果对强制类型转换操作符的调用会丢失精度而该操作符承诺不丢失精度。
5.8 参数的设计
要用类层次结构中最接近基类的类型作为参数的类型,同时要保证该类型能够提供成员所需的功能。
不要使用保留参数。
不要把指针、指针数组及多维数组作为公有方法的参数。
要把所有的输出参数放在所有以值方式和以引用方式传递的参数的后面(不包括参数数组),即使这样会在重载成员之间导致参数顺序不一致也要如此。
要在覆盖成员或者实现接口成员时保持参数命名的一致。
要用枚举 - 如果不这样做会导致参数中有两个或两个以上的布尔类型。
不要使用布尔参数,除非百分之百肯定绝对不需要两个以上的值。
考虑在构造函数中,对确实只有两种状态的参数以及用来初始化布尔属性的参数使用布尔类型。
要对传给共有的、受保护的或显式实现的成员的参数进行验证。如果验证失败,那么应该抛出 System.ArgumentException 或其子类。
要抛出 ArgumentNullExcepytion - 如果传入的是 null 而该成员不支持 null。
要验证枚举参数。
不要用 Enum.IsDefined 来检查枚举的范围。
要清楚地知道传入的可变参数可能会在验证后发生改变。
避免使用输出参数或引用参数。
不要以引用方式传递引用类型。
考虑给数组参数增加 params 关键字 - 如果预计用户会传入为数不多的数组元素。
避免使用 params 数组参数 - 如果绝大多数时候调用方要传入的数组元素本来就已经在一个数组中了。
不要使用 params 数组参数 - 如果要在成员中对数组进行修改。
考虑在简单的重载中使用 params 关键字,尽管更复杂的重载不能用 params 关键字。
要对参数进行合理的排序,以便使用 params 关键字。
考虑在对性能要求非常高的 API 中为参数数量较少的调用提供特殊的重载和相应的实现。
要注意传入的 params 数组参数可能是 null。
不要使用 varargs 方法,又称省略号。
要为任何以指针为参数的成员提供一个替补成员,这是因为指针不符合 CLS 规范。
避免对指针参数进行高开销的检查。
要在设计用到指针的成员时遵循与指针相关的常用约定。
《.NET 设计规范》第 5 章:成员设计