1.1 简介
本文档可作为编写规范的C#代码指南,可以使用规则描述的条目列入公司
的FxCop 的规则,程序员必须修改由FxCop 检查出来的以及SQA、同行评审检
查出来的代码规范问题,注释规范符合NDoc 的要求,程序文档的生成工具采用
NDoc,本文所述的许多原则也可以作为其它程序语言的编码的指南。
1.2 文件组织
1.2.1 C# 源文件
保持文件短小,不要超过2000 行代码,保持目录结构清晰。将每个类放到单
独的文件中,类名与文件名一致(使用.cs 作为扩展名).
1.2.2 目录层次结构
项目名称允许与目录名或目录层次不一致,项目内部所有子命名空间必须存
储在相应的子目录下,例如:项目名称为Thunderbird.Public 可以保存在c:/test
目录下,但是Thunderbird.Public 项目中的子命名空间Thunderbird.Public.Data 必
须保存在c:/test/data 目录中。
1.3 缩进
1.3.1 换行
当表达式过长,不适合使用单行时,按以下通用原则换行:
在逗号后换行
在操作符前换行
使用高级别的换行
另起的新行与前一行表达式开始的位置对齐
方法调用的分行例子:
longMethodCall(expr1, expr2,
expr3, expr4, expr5);
算术表达式的分行例子:
1、好的分行风格:
var = a * b / (c - g + f)
+ 4 * z;
2、不好的分行风格(尽量避免):
var = a * b / (c - g +
f) + 4 * z;
第一种分行方式,分行将()内的内容分开了(不符合高级别换行的原则)
1.3.2 对齐
代码编写完毕后,全选当前代码,使用.net 开发环境中的编辑>>高级>>格式
化选定的内容进行自动格式化和对齐。
1.4 注释
1.4.1 注释
注释采用//,.net 开发环境下工具条分别为注释选定行和取消注释选定行。
1.4.2 可文档化注释
项目中所有的类以及类中的公有属性、方法都应进行注释,注释可以在IDE 中
连续输入///实现自动化的注释格式生成
微软介绍了一种基于XML格式可文档化的注释,这些注释包含了XML 标记,
下面是部分单行标准注释方法。
类、方法概述
/// <summary>
/// 这是一下关于。。。
/// </summary>
多行的XML注释例如下面
/// <exception cref=”BogusException”>
/// This exception gets thrown as soon as a
/// Bogus flag gets set.
/// </exception>
作为可文档化的XML注释,所有行必须使用三个斜杠
编译器将处理任何为有效 XML 的标记。下列标记提供了用户文档中常用的功能:
<c> <para> <see>1
<code> <param>1 <seealso>1
<example> <paramref>1 <summary>
<exception>1 <permission>1 <value>
<include>1 <remarks>
<list> <returns>
详细请参见:(直接在IE 里输入即可)
ms-help://MS.NETFrameworkSDKv1.1.CHS/csref/html/vclrfTagsForDocumentation
Comments.htm
1.5 声明
1.5.1 每行的声明个数
一个变量一行,并且变量的含义尽量清楚,如果不是简单明了的变量必须有
足够的注释注明含义。
例如
int level; //树的层次
int size; //表的大小
1.5.2 变量初始化
本地变量在定义后立即初始化,例如:
string name = myObject.Name;
int val = time.Hours;
1.5.3 类和接口声明
当编写C#类和接口时,必须遵守以下格式化规则:
在“(”、方法名、第一个参数之间不要有空格。
开始的“{”出现在申明语句的下一行。
结束的“}”单独一行和开始的“}”对齐。
如下所示:
Class MySample : MyClass, IMyInterface
{
int myInt;
public MySample(int myInt)
{
this.myInt = myInt ;
}
void Inc()
{
myInt++;
}
void EmptyMethod()
{
}
}
1.6 语句
1.6.1 简单语句
每行应只包含一个语句
1.6.2 返回语句
返回语句不要包含外括号,不使用诸如下面这种形式:
return (n * (n + 1) / 2);
而使用:
return n * (n + 1) / 2;
1.6.3 If, if-else, if else-if else 语句
if, if-else 和 if else- if else 语句采用以下形式书写:
if 语句
if (condition)
{
DoSomething();
...
}
If-else 语句
if (condition)
{
DoSomething();
...
}
else
{
DoSomethingOther();
...
}
If – else if - else 语句
if (condition)
{
DoSomething();
...
}
else
{
if (condition)
{
DoSomethingOther();
...
}
else
{
DoSomethingOtherAgain();
...
}
}
1.6.4 For / Foreach 语句
For 语 句采用以下格式:
for (int i = 0; i < 5; ++i)
{
...
}
或单行格式(考虑使用 while 语句代替) :
for (initialization; condition; update) ;
foreach 语句采用以下格式:
foreach (int i in IntList)
{
...
}
注: 一般情况下,即使循环中只有一个语句也要使用{}.
1.6.5 While/do-while 语句
While 语句采用以下书写格式:
while (condition)
{
...
}
空的while 语句采用格式:
while (condition) ;
do-while 语句采用以下格式:
do
{
...
} while (condition);
1.6.6 Switch 语句
switch 采用以下格式:
switch (condition)
{
case A:
...
break;
case B:
...
break;
default:
...
break;
}
1.6.7 Try-catch 语句
try-catch 语句采用以下格式:
try
{
...
}
catch (Exception)
{}
or
try
{
...
}
catch (Exception e)
{
...
}
或采用这种格式
try
{
...
}
catch (Exception e)
{
...
}
finally
{
...
}
1.7 空格使用
在方法的每个变量后空一格,所有操作符的两侧必须加上空格
如TestMethod(a, b, c);
不要使用
TestMethod(a,b,c);
或者
TestMethod( a, b, c );
a = b; //不要写成a=b
for (int i = 0; i < 10; ++i) // 不要写成 (int i=0; i<10; ++i)
// 或 for(int i=0;i<10;++i)
1.8 命名规范
1.8.1 大写风格
1.8.1.1 Pascal 格式
变量中单词首字母大写(例如:TestCounter).
1.8.1.2 Camel 格式
除变量的第一个字母外,以大写字母书写变量中单词的第一个字母例如
testCounter.
1.8.1.3 全部大写
只有常量使用,全部大写
例如
public class Math
{
public const PI = ...
public const E = ...
public const FEIGENBAUMNUMBER = ...
}
1.8.1.4 命名原则
通常情况下在变量名中使用”_”和根据匈牙利命名法命名变量被认为是不好
的习惯。
匈牙利命名法定义了一系列前缀和后缀映射变量的类型,这种风格的命名方
法被广泛应用于早期的windows 程序,但现在已经不被建议,本规范命名规则不
允许使用该命名方法。必须记住:一个好的变量是表明它的含义而不是类型,名
称应可以自解释。采用中文拼音的必须使用全部拼音字母,建议采用含义明确的
英文单词及短词。
本命名规则对于GUI 代码例外的,所有域和变量名包含GUI 元素,象按钮
必须加非缩写的类型后缀。如下所示:
System.Windows.Forms.Button cancelButton;
System.Windows.Forms.TextBox nameTextBox;
例外:与数据库交互的GUI 元素,可以直接与数据库字段列名一致,而不管类
型。
1.8.2 类命名原则
类名称必须是名词或名词短语.
使用PASCAL 大写风格,详见1.8.1.1
不要使用任何类前缀
1.8.2.1 接口命名原则
用名词、名词短语、描述行为的形容词命名接口(例如: IComponent or
IEnumberable),以I 作为前缀,使用Pascal 大写风格(详见1.8.1.1)
1.8.2.2 Enum 命名原则
Use Pascal Casing for enum value names and enum type names
不要使用前缀或后缀Don’t prefix (or suffix) a enum type or enum values
使用单数的命名,使用复数的命名
Use singular names for enums
Use plural name for bit fields.
1.8.2.3 ReadOnly and Const 命名
用名词,名词短语命名或名词缩写命名只读和常数,大写风格使用Pascal
Casing (详见1.8.1.1)
1.8.2.4 Parameter/non const field Names
Do use descriptive na mes, which should be enough to determine the variable
meaning and it’s type. But prefer a name that’s based on the parameter’s
meaning.
Use Camel Casing (see 8.1.2)
1.8.2.5 变量命名
采用 Camel 大写风格 (详见1.8.1.2)
1.8.2.6 Method 命名
采用动词或动词短语命名方法.
大写风格采用Pascal 风格 (详见1. 8.1.2)
1.8.2.7 Property 命名
采用名词或名词短语命名,
大写风格采用Pascal 风格(详见1. 8.1.2)
1.8.2.8 Event 命名
命名event handlers 采用 EventHandler 后缀
使用两个参数,分别为sender 和 e
使用Pascal 大写风格(详见1.8.1.1)
1.8.2.9 大写风格总结
类型 大写风格
Class / Struct Pascal
Interface Pascal 风格加前缀I
Enum type and values Pascal
Events Pascal
public Fields Pascal
Methods Pascal
Namespace Pascal
Property Pascal
Protected/private Fields Camel
Parameters Camel
1.9 编码指南
编码指南部分作为编码实践的指导,可以参照执行,其中1.9.2,1.9.3,1.9.9,
1.9.7 必须执行。
1.9.1 一般性规范’
Ø 一个文件不要超过500 行的代码(不包括机器产生的代码)。
Ø 一个方法的代码长度不要超过25 行。
避免方法中有超过5 个参数的情况。使用结构来传递多个参数。
Ø 每行代码不要超过80 个字符。
Ø 不要手工的修改机器产生的代码。
a) 如果需要编辑机器产生的代码,编辑格式和风格要符合该编码标准。
1.9.2 数据库有关代码
所有与数据库有关的sql 代码,必须使用单独得类文件,建议按业务对象对数据
库操作分类形成数据存取类,类名采用DB 作为后缀,并使用公司公用的
Thunderbird.Public.Data 命名空间里提供的方法进行数据库相关操作。
1.9.3 避免硬编码
Ø 不要硬编码数字的值,总是使用构造函数设定其值
public class MyMath
{
public const double PI = 3.14159...
}
Ø 表现给最终用户的字符串不要使用硬编码而要使用资源文件替换之。
Ø 不要硬编码可能更改的基于配置的字符串,比如连接字符串。
1.9.4 变量定义
Ø 不要申明任何实例或类变量为public,采用private,private 是缺省的可以省略,
如果需要改变变量采用属性代替。静态的变量作为一个例外可以定义为
public
Ø 当需要构建长的字符串的时候,使用StringBuilder 不要使用string
Ø 只有是自然结构才能直接使用const,比如一个星期的天数。
Ø 避免在只读的变量上使用const。如果想实现只读,可以直接使用readonly
或者利用属性实现。
public class MyClass
{
public readonly int Number;
public MyClass(int someValue)
{
Number = someValue;
}
public const int DaysInWeek = 7;
}
1.9.5 接口
Ø 在一般情况下不要定义有限制符的接口。接口的限制级别通常可以用强类型
来替换之。
public class Customer
{…}
//避免:
public interface IList<T> where T : Customer
{…}
//正确:
public interface ICustomerList : IList<Customer>
{…}
Ø 不确定在接口内的具体方法的限制条件。
Ø 类和接口中的方法和属性至少为2:1 的比例。
Ø 尽量使每个接口中包含3-5 个成员。
Ø 接口中的成员不应该超过20 个。
Ø 避免接口成员中包含事件。
Ø 避免使用抽象方法而使用接口替换。
Ø 在类层次中显示接口。
Ø 使用显式的接口实现。
Ø 从不假设一个类型兼容一个接口。Defensively query for that interface.
SomeType obj1;
IMyInterface obj2;
/* 假设已有代码初始化过obj1,接下来 */
obj2 = obj1 as IMyInterface;
if (obj2 != null)
{
obj2.Method1();
}
else
{
//处理错误
}
1.9.6 异常处理
Ø 只抛出已经显式处理的异常。
Ø 在捕获(catch)语句的抛出异常子句中(throw),总是抛出原始异常维护原始错
误的堆栈分配。
catch(Exception exception)
{
MessageBox.Show(exception.Message);
throw ; //和throw exception 一样。
}
Ø 避免方法的返回值是错误代码。
Ø 尽量避免定义自定义异常类。
Ø 当需要定义自定义的异常时:
a) 自定义异常要继承于ApplicationException。
b) 提供自定义的序列化功能。
1.9.7 枚举变量
Ø 避免给枚举变量提供显式的值。
//正确方法
public enum Color
{
Red,Green,Blue
}
//避免
public enum Color
{
Red = 1,Green = 2,Blue = 3
}
Ø 避免指定特殊类型的枚举变量。
//避免
public enum Color : long
{
Red,Green,Blue
}
1.9.8 类型转换
Ø 避免在继承中使用new 而使用override 替换。
Ø 避免显示的转换,使用as 操作符进行兼容类型的转换。
Dog dog = new GermanShepherd();
GermanShepherd shepherd = dog as GermanShepherd;
if (shepherd != null )
{…}