类是引用类型的一般情况,占了框架中的大多情况,类的流行归于它支持面向对象的特征,以及它的普遍的适用性,基类和抽象类是两个特殊的逻辑分组,它们与扩张性有关。

由于CLR不支持多继承,接口类型可以用来模拟多继承,既能被引用类型实现,也能被值类型实现。

结构是值类型的一般情况,应该用于小而简单的类型,就像编程语言的基本类型一样。

枚举是值类型的一个特例,它用来定义一小组值。

静态类是那些用来容纳静态成员的类型,常用来提供对其他操作的快速访问。

委托、异常、Attribute、数据、集合都是引用类型的特例,各有各自的用途。

  • **√ ** 确保每个类型由一组定义明确、相互关联的成员组成,而不要仅仅是一些无关功能的随机集合。

4.1.类型和名字空间

在设计大型框架之前,应该决定如何将功能划分到一组功能域中,这些功能域由名字空间表示,为了确保一组有条理的名字空间包含的类型能很好的集成,不发生冲突,以及不会重复,自顶向下的设计很有必要。导致了下面的规范:

  • 用名字空间把类型组织成一个相关的特性域的层次结构。
  • × 避免 非常深的名字空间层次(难于浏览,需要经常回溯)
  • × 避免 有太多的名字空间
  • × 避免 把为高级方案而设计的类型和常见的编程任务而设计的类型放在同一个名字空间中。

    (方便用户更容易理解框架的基本概念,而且更容易在常见的场景中使用框架)
  • × 不要 不指定类型的名字空间就定义类型。

标准子名字空间的命名

很少使用的类型应该放在子名字空间中,以免扰乱主名字空间,我们确定了几组类型,应该把它们从主名字空间中区分离出来。

  1. .Design 子名字空间

    仅用于设计时的类型应该放在名为 .Design 的子名字空间。

    如:System.Windows.Forms.Design;

    System.Messaging.Design;
  2. .Permissions子名字空间

    权限类型应该放在 .Permissions 子名字空间。
  3. .Interop子命名空间

    许多框架需要支持与旧系统的互操作性(interoperability)。

4.2 类和结构之间的选择

引用类型在堆上分配,由垃圾收集器管理;而值类型要么在栈上分配并在栈展开时释放,要么内联在容纳它的类型中并在容纳它的类型被释放时释放。因此,与引用类型的分配与释放相比,值类型的分配与释放开销更低。

引用类型的数组不是非内联分配的,意为数组元素只是一些引用,指向那些位于堆中的引用类型的实例。而值类型的分配是内联的,数组的元素就是值类型的真正实例。因此值类型的分配和释放的开销要比引用类型的大的多,在大多情况下,值类型数组具有更好的局部性。

值类型在被强制转换为对象或装箱。因为装箱和对象是在堆上分配的,且由垃圾收集器管理,所以太多的装拆箱操作会对堆、垃圾收集器,并对系统性能造成影响。相比之下,在对引用类型执行转换操作,不会发生装箱操作。

引用类型的赋值是复制引用,而值类型的赋值复制整个值,对大的引用类型复制开销要比值类型小的多

引用类型是引用传递,值类型是值传递。改变引用类型的一个实例会影响其他的实例,改变值类型的实例,不会影响到它的副本。

框架中的大多数类型应该是类,但是在某些特殊情况下,由于值类型所具有的特征,使用结构更合适。

  • 考虑 定义结构而不是类 —— 如果该类型的实例比较小,生命周期比较短,经常被内嵌在其他对象中。

  • × 避免 定义结构,除非该类型具有以下特征:

    • 它在逻辑上代表一个独立的值,与基本类型相似(int)

    • 它的实例大小小于16字节

    • 它是不可变的

    • 它不需要被经常装箱

在所有的其他情况下,应该将类型定义为类。

4.3 类和接口之间的选择

一般来说,类是用来暴露抽象的优先选择。

  接口的缺点在于当需要允许API不断演化时,它的灵活性不如类,一旦你发布了一个接口,它的成员就永远固定了,给接口添加任何东西都会破坏已经实现该接口的已有类型。

  类提供了更多的灵活性,你可以给一个已发布的类添加成员。只要添加的方法不是抽象的,任何已有的派生类无需改变仍能继续使用。

  • 优先采用类而不是接口

    与基于接口的API相比,基于类的API容易演化得多,因为可以给类型添加成员而不会破坏已有的代码。

  • 用抽象类而不是接口来解除协定与实现之间的耦合。

    抽象类经过正确的设计,同样能够解除协定与实现之间的耦合,与接口能达到的程度不相上下。

  • 定义接口,如果需要提供一个多态的值类型层次结构的话。

    值类型不能自其它类型继承,但是她们可以实现接口。

public struct Int32 : IComparable,IFormattable,IConvertible {...}
  • 考虑 通过定义接口来达到与多重继承相类似的效果。

4.4 抽象类的设计

  • × 不要 在抽象类型中定义公有的或内部受保护的构造函数。

    只有当用户需要创建一个类型的实例时,该类型的构造参数才是公有的,由于你无法创建一个抽象类的实例,因此如果抽象类型具有公有构造函数,那么这样的设计不仅错误,而且会误导用户。
//错误设计
public abstract class Claim {
public Claim(){}
}
//好设计
public abstract class Claim {
protected Claim(){}
}
  • 为抽象类定义受保护的构造函数或内部构造函数。

    更常见的情况是受保护的构造函数,唯一的目的是允许子类型被创建时,基类能够做自己的初始化。
public abstract class Claim {
protected Claim(){
...
}
}

内部构造函数可以用来把该抽象类的具体实现限制在定义该抽象类的程序集中。

public abstract class Claim {
internal Claim(){
...
}
}
  • 为发布的抽象类提供至少一个继承自该类的具体类型。

    这有助于验证该抽象类的设计是否正确。例如,System.IO.FileStreamSystem.IO.FileStream 抽象类的一个实现。

4.5 静态类的设计

静态类定义为一个只包含静态成员的类。

如果一个类被定义为静态,那么它就是密封的、抽象的,不能覆盖或者声明任何实例成员。

  静态类是在纯面向对象设计和简单性之间的一个权衡,它们被广泛用来提供一下访问其他操作(比如System.IO.File)的快捷方式,存放扩展方法,或者以一种不完全面向对象的方式来提供一些功能。(System.Enviroment)

  • 尽量少用静态类

    静态类仅被用作辅助类,来支持框架的面向对象的核心。

  • × 不要 把静态类当做杂物箱。

    每一个静态类都应该有其明确的目的。

  • × 不要 声明或覆盖(override)静态类中的实例成员。

  • 把静态类定义为密封的、抽象的,并添加一个私有的实例构造函数。

4.6 接口的设计

虽然大多数情况下API用类或结构来构建最好,但是在有些情况下,接口更合适。甚至某些情况接口是唯一的选择。

  CLR 不支持多继承,但允许类型实现一个或多个接口,因此通常用接口来实现多继承。

  另一种适合定义接口的情况是,为多种类型(包括值类型)创建一个公共接口。虽然值类型无法继承除了 System.ValueType 之外的其他类型,但他们可以实现接口,所以提供了一个公共的基类型,使用接口是唯一的选择。

public struct Boolean : IComparable {...}
  • 定义接口,如果你需要包括值类型在内的一组类型支持一些公共的API。

  • 考虑 定义接口,如果需要让已经继承自其它类型的类型支持该接口提供的功能。

  • × 避免 使用记号接口(没有成员的接口)

//避免
public interface IImmutable {} //空接口
public class Key : IImmutable{...}
//考虑
[IImmutable]
public class Key{...}
  • 为接口提供至少一个实现该接口的类型。

  • 为你定义的每个接口提供至少一个使用该接口的API(一个以接口为参数的方法或是一个类型为该接口的属性)

    例如,List<T>.Sort 使用了 IComparer<T> 接口。

  • × 不要 给已发行的接口再添加成员。

    这样做会破坏该接口的实现,为了避免版本的问题,应该创建一个新的接口。

    一般来说,在为托管代码设计可重用的程序库时,你应该选择类而不是接口。

4.7 结构的设计

通用目的的值类型通常称为 struct(结构)。

  • × 不要 为结构提供默认的构造函数。(C#不允许结构有默认的构造函数)

  • × 不要 定义可变的值类型。

  • 确保所有的实例数据都为0,false,或null时,结构仍处于有效状态。(可以防止在创建一个结构时创建出无效的实例)

  • 为值类型实现 IEquatable<T>

    值类型的 Object.Equals 方法会导致装箱,默认的实现并不高效,因为使用了反射,IEquatable<T>.Equals 性能好的多,不会导致装箱。

  • × 不要 显示的扩展 System.ValueType,实施上大多数编程语言步允许这么做。

4.8 枚举的设计

枚举是一种特殊的值类型,有两种类型的枚举:简单枚举标记枚举(flag enum)。

简单枚举 代表小型的、闭合的一组选择。例如(一组颜色):

Public enumColor{
Red,
Green,
Blue,
……
}

标记枚举 的设计是为了支持对枚举值进行按位操作。标记枚举的常见例子是一个选择列表,

[Flags]
Public enumAttributeTargets
{
Assembly=0x0001,
Module=0x0002,
Cass=0x0004,
Struct=0x0008
}
  • 用枚举来加强那些表示值的集合的参数、属性以及返回值的类型性。

  • 优先使用枚举而不要使用静态常量。(枚举是一个包含一组静态常量的结构)

  • × 不要 把枚举用于开放的集合(比如操作系统版本、朋友的名字等)

  • × 不要 提供为了今后使用而保留的枚举值。

  • × 避免 显示的暴露只有一个值的枚举。

  • × 不要sentinel 值包含在枚举值中。

  • 为简单枚举类型提供零值。(应该考虑把该值称为 None 之类的东西,如果这样的值不适合用于某个特定的枚举,那么应该把该枚举中最常用的默认值赋值为0)

public enum Compression{
None = 0,
Gzip,
Deflate,
}
  • 考虑Int32 作为枚举的基本实现类型。

  • 用复数名词或者名词短语来命名标记枚举,用单数名词或者名词短语来命名简单枚举

  • × 不要 直接扩充 System.Enum

    System.Enum 是一个特殊的类型,被 CLR 用来创建用户定义的枚举。

4.8.1 标记枚举的设计

  • 对标记枚举使用 System.FlagsAttribute,不要把该 attribute 用于简单枚举。
[Flags]
Public enum AttributeTargets
{...}
  • 用2的幂次方作为标记枚举的值,这样就可以通过按位或操作自由组合他们。
[Flags]
Public enum WatcherChangeTypes
{
Created=0x0002,
Deleted=0x0004,
Changed=0x0008,
Renamed=0x00010,
}
  • 考虑 为常用的标记组合提供特殊的枚举值。

    位操作是一个高级概念,对应简单任务来说不是必须的,FileAccess.ReadWrite 就是这样一个例子
[Flags]
Public enum FileAccess
{
Read=1,
Write=2,
ReadWrite= Read | Write,
}
  • × 避免 让创建的标记枚举包含某些无效的组合。

  • × 避免 把0用作标记枚举的值,除非该值表示“所有标记都被清除“,而且按下一条规范进行了适当的命名。

C#中字面常量0可以隐式地转换为任何枚举类型,因此你可以编写这样的代码:

if (Foo.SomeFlag == 0) ...

CLR规定任何值类型的默认值“所有的位都清零“。

  • 把标记枚举的零值命名为 None,对其标枚举来说,该值必须始终意味着“所有标记均被清除”。
[Flags]
Public enum BorderStyle
{
Fixed3D=0x1,
FixedSingle=0x2,
None=0x0
}
if(foo.BorderStyle == BorderStyle.None) ...

但是,该规则只适用于标记枚举,对于非标记枚举的情况,避免使用0值实际上是不利的,所有的枚举类型一开始都为零值。

4.8.2 给枚举添加值

常会发现在需要在程序发行之后需要给一个枚举添加值。如果新添加的值是一个已有API的返回值,那么就存在潜在的应用程序兼容性问题。

  • 考虑 给枚举添加值,尽管有那么一点兼容性的风险。

如果有实际数据,表明给枚举添加值会导致应用程序的不兼容,可以考虑添加一个新的API来返回新老枚举值,这样就能确保仍然兼容现有的应用程序。

4.9 嵌套类型

嵌套类型是一个定义在另一个类型的作用域内的类型。另一个类型被称为外层类型。嵌套类型能够访问外层类型的所有成员。可以访问定义在外层类型的私有字段以及定义在外层类型的所有父类的受保护字段。

  一般来说,尽量少用嵌套类型,嵌套类型与外层类型紧密耦合,不适合将它们作为通用类型。嵌套类型适合用来对它们的外层类型的实现细节建模。

  • 在想让一个类型能够访问外层类型的成员时才使用嵌套类型。

  • × 不要 用嵌套类型进行逻辑分组,应该用名字空间来达到此目的。

  • × 避免 公开的暴露嵌套类型,唯一的例外是如果只需要在极少数的场景中声明嵌套类型的变量,比如派生子类,或者其他高级自定义场景中。

    一般避免使用嵌套类型,只有在开发人员几乎不需要声明该类型的变量时才使用嵌套类型。(例如集合的枚举器)

  • × 不要 使用嵌套类型,如果该类型可能会被除了它的外层类型之外的类型引用。

  • × 不要 使用嵌套类型,如果它们需要被客户代码实例化。

  • × 不要 把嵌套类型定义为接口的成员。

一般来说尽量少用嵌套类型,而且应该避免将嵌套类型公开暴露给外界。

4.类型设计规范《.NET设计规范》的更多相关文章

  1. Phone APP设计规范/iPad APP设计规范/Android APP设计规范/网页设计规范

    原文链接:http://www.ui001.com/chicun/ ①iPhone的设计尺寸 iPhone界面尺寸: 设备 分辨率 状态栏高度 导航栏高度 标签栏(工具栏)高度 iPhone6 plu ...

  2. 19条ANDROID平台设计规范平台设计规范

    1.尺寸以及分辨率: Android的界面尺寸比较流行的有:480*800.720*1280.1080*1920,我们在做设计图的 时候建议是以 480*800的尺寸为标准: 2.界面基本组成元素: ...

  3. .NET设计规范————类型设计规范

    类型设计规范 从CLR的角度看,只有值类型和引用类型两种类型,但是从框架设计的角度我们把类型从逻辑上分了更多的组.如下所示: 类是引用类型的一般情况,占了框架中的大多情况,类的流行归于它支持面向对象的 ...

  4. 《.NET 设计规范》第 4 章:类型设计规范

    第 4 章:类型设计规范 4.1 类型和命名空间 要用命名空间把类型组织成一个由相关的功能区所构成的层次结构中. 避免非常深的命名空间层次.因为用户需要经常回找,所以这样的层次浏览起来很困难. 避免有 ...

  5. NET设计规范二:类型成员设计

    http://www.cnblogs.com/yangcaogui/archive/2012/04/20/2459567.html 接着 → .NET设计规范一:设计规范基础 上一篇,我们来了解下类型 ...

  6. JavaScript导航树

    JS导航树 整理之前的小代码片段,放到博客,便于以后完善查看: 该JS导航树实际效果,[GSP+社区网站专题课程页面导航树]地址:http://gsp.inspur.com/knowledge/zhu ...

  7. MySQL数据库开发规范知识点

    前言: 设计规范更多的是为了确保数据库设计的合理性.为了项目最终的协调稳定性,而命名规范则更多的是为了确保设计的正式和统一. 约定优先于配置(Convention Over Configuration ...

  8. ****如何优雅的用Axure装逼?高保真原型心得分享

    本文核心内容点:- 啥是高保真原型?(附简单说明原型)- Axure可以画出什么水准的高保真?(给示例,开启装逼模式)- 高保真原型图技巧:- 啥时候上高保真?适用场景 and 不适用场景 啥是高保真 ...

  9. 建议收藏 - 专业的MySQL开发规范

    为了项目的稳定,代码的高效,管理的便捷,在开发团队内部会制定各种各样的规范 这里分享一份我们定义的MySQL开发规范,欢迎交流拍砖 数据库对象命名规范 数据库对象 命名规范的对象是指数据库SCHEMA ...

随机推荐

  1. JVM学习笔记(四):类加载机制

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 一.类加载的时机1. 类从被加载到虚拟机内存 ...

  2. BZOJ2768 JLOI2012冠军调查(最小割)

    容易想到网络流.将每个人拆成0和1两个点.若某人值为0的话则让源连向0,否则让1连向汇,流量为1.相互认识的人之间01各自连边.跑最小割即可. #include<iostream> #in ...

  3. 自动化运维—Ansible(上)

    一:为什么选择Ansible 相对于puppet和saltstack,ansible无需客户端,更轻量级 ansible甚至都不用启动服务,仅仅只是一个工具,可以很轻松的实现分布式扩展 更强的远程命令 ...

  4. Django_博客项目 引入外部js文件内含模板语法无法正确获取值得说明和处理

    问题描述 : 项目中若存在对一段js代码复用多次的时候, 通常将此段代码移动到一个单独的静态文件中在被使用的地方利用 script 标签的 src 属性进行外部调用 但是如果此文件中存在使用 HTML ...

  5. MT【177】三个乘积和

    对任意 2 个 1,2,3,4,5,6 的全排列 $(a_1,a_2,a_3,a_4,a_5,a_6)$ 和 $(b_1,b_2,b_3,b_4,b_5,b_6)$,求$\displaystyle S ...

  6. 洛谷 P4070 [SDOI2016]生成魔咒 解题报告

    P4070 [SDOI2016]生成魔咒 题目描述 魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示.例如可以将魔咒字符 \(1\).\(2\) 拼凑起来形成一个魔咒串 \([1,2]\). 一个魔咒 ...

  7. suoi44 核能显示屏 (cdq分治)

    首先二维树状数组肯定开不下 仿照二维树状数组的做法,如果有差分数组$d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]$,那么就有: $$sum[x][y] ...

  8. (转)Maven学习总结(九)——使用Nexus搭建Maven私服

    孤傲苍狼只为成功找方法,不为失败找借口! Maven学习总结(九)——使用Nexus搭建Maven私服 一.搭建nexus私服的目的 为什么要搭建nexus私服,原因很简单,有些公司都不提供外网给项目 ...

  9. AsynchronousFileChannel 使用的默认线程池的疑问

    AIO服务在线上测试有一周时间了吧,现在发现一个问题,通过“任务管理器”查看aio服务的进程可以看出该进程的当前线程数经过几天的运行,在不断的增加: 1. 刚刚启动的时候,线程数在16个左右 2. 经 ...

  10. OpenStack API部分高可用配置(一)

    一.概况与原理  SHAPE  \* MERGEFORMAT 1)所需要的配置组件有:pacemaker+corosync+HAProxy 2)主要原理:HAProxy作为负载均衡器,将对openst ...