C#程序书写规范
C#程序书写规范
6.3 If, if-else, if else-if else 语句. 8
1. 关于C# 型代码指南
这个文档是编写稳定可靠的程序的指南。该文章主要着眼于用C# 编写的程序,但其中许多的规则和原理甚至在使用其他编程语言时也十分有用。
2. 文件系统(文件结构)
2.1 C# 源文件
尽可能让类名或者文件名短小,不要超过2000个字符。把每一个类作为一个单独的文件,并且用类的名字来命名该文件(当然要加后缀名.cs)。这个约定可以使得命名更加容易。
2.2路径布局
给每一个namespace创建一个路径。(列如 用MyProject/TestSuite/TestTier作为文件MyProject.TestSuite.TestTier的路径,不要使用namespace名字中的点)。这样可以更容易规划namespace的路径。
3. 缩进
3.1 wrapping lines
当一个表述无法用一行写完时,依照下列常用规则把它分段:
u 在逗号后断开
u 在运算符前断开
u 使用高级断点而非低级断点
分段方法的示例:
longMethodCall(expr1, expr2, expr3,
expr4, expr5);
算术表达式分段的示例:
推荐:
var = a * b / (c - g + f)
+ 4 * z;
避免如下坏形式:
var = a * b / (c - g
+ f) + 4 * z;
第一个是标准的,由于断点出现在加上括弧的表达式外边(高级准则)。
3.2 空格
标准的使用空格作为行首缩进没有固定的格数。有些人喜欢2个空格,有些喜欢4个空格,甚至有些是8个或者更多的空格。
现在规定,使用VS.NET默认的4个空格作为缩进。在缩进的时候,更多的希望使用空格而不是TAB作为缩进。
4. 注释
4.1 块注释
通常我们避免使用块注释。推荐用///的形式来给出标准的C#注释。若要使用块注释,那么其形式如下:
/* 第一行
*第二行
*第三行
*/
这样对于读者来说,块就从代码变成了可读的文档。相类似的,对于单行的注释你可以使用这种老式的C语言的注释方式,尽管这个是不推荐使用的。但是在这种注释方式下,就必须根据注释内容来分行,否则就无法通过同一行的注释了解代码的运行了:
/* blah blah blah */
块注释只在少数几种情况下才有用,例子参见TechNote“The fine Art of Commenting”。一般来讲,对于段落代码使用块注释很有效。
4.2 单行注释
使用//形式来注释单行代码(VS.NET对于单行注释有快捷键,CTRL+KC)。它也可以用来给段落代码加注释。对代码文件使用单行的注释时,必须根据缩进级别来缩进。被注释的代码必须在第一行中加以注释,由此增加被注释代码的可读性。一般注释的长度不可以比编码解释长很多。
4.3 文件注释
在.net的框架下,微软引进了一种在XML 注释基础上的文件生成系统。这种注释形式上就是包括XML标识的C#单行注释。单行注释有下面这种形式:
/// <summary>
/// This class...
/// </summary>
多行XML注释有这样的形式:
/// <exception cref=”BogusException”>
/// This exception gets thrown as soon as a
/// Bogus flag gets set.
/// </exception>
所有的行必须加上///才能被认为是XML注释行。
XML注释标识分为2类:
l 文档元素
l 格式化/参照
第一类包括类似于<summary>, <param>或者<exception>的元素。这些元素是必须写文档的,这样相关的程序才能产生可以给程序员阅读的文档。后一类主管文件设计,使用类似于<code>,<list>或者<para>的标识,生成的文件是HTML HELP格式的。若须关于XML注释详解参看微软.net框架SDK文件。
5 定义变量
5.1每行定义变量的个数
推荐每行定义一个变量,这样便于注释。换句话说,
int level; // indentation levelint size; // size of table
定义变量时,不要在一行中放入多个变量或者是不同类型的变量。
例如:
int a, b; //'a'是什么? 'b'代表什么?
这个例子也显示了变量名字不显然的弊端。命名变量时一定要清楚这点。
5.2初始化
局部变量在定义的时候就要初始化。例如:
string name = myObject.Name;
或者
int val = time.Hours;
注:如果要初始化一个对话框,同时需要释放资源。要使用using 语句:
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
//…
}
5.3类,接口和namespace的定义
当编译C# 的类,接口和namespace的时候,必须按照一下几个初始化原则:
l 左大括号“{”出现与定义语句的下一行
l 右大括号“}”单独成一行,与相应的左大括号配套。
例如:
class MySample : MyClass, ImyInterface
{
int myInt;
}
namespace Mynamespace
{
// Namespace contents
}
关于大括号放置的示例参看10.1节。
5.4定义method
关于定义method,要应用括号放置原则,而且在method的名字跟括号“(”之间不可以有空格,“(”后边跟参数表。
例如:
public MySample(int myInt)
{
this.myInt = myInt;
}
void Inc()
{
++myInt;
}
5.4定义属性,索引器和事件
对于属性,indexer和事件的定义,在定义的同行不可以用任何括号。例如:
public int Amount
{
get
{
...
}
set
{
...
}
}
public this[string index]
{
get;
set;
}
public EventHandler MyEvent
{
add
{
...
}
remove
{
...
}
}
6 语句
6.1简单语句
每一行仅有一个语句。
6.2 Return语句
Return语句使用时,外面不可以加圆括号。
不用:return (n*(n+1)/2) ;
用 :return n*(n+1)/2 ;
6.3 If, if-else, if else-if else 语句
If , if-else, if else-if else 语句的形式应该是这样的:
if (condition)
{
DoSomething();
...
}
if (condition)
{
DoSomething();
...
}
else
{
DoSomethingOther();
...
}
if (condition)
{
DoSomething();
...
}
else if (condition)
{
DoSomethingOther();
...
}
else
{
DoSomethingOtherAgain();
...
}
6.4 For/Foreach语句
For语句有下面这种形式:
for (int i = 0; i < 5; ++i)
{
...
}
或者单行的(考虑使用while语句来代替):
for (initialization; condition; update)
;
foreach语句为:
foreach (int i in intList)
{
...
}
注:甚至循环中只有一个语句时,一般都使用括号
6.5 While/do-while 语句
while语句为:
while (condition)
{
...
}
空的while 语句为:
while (condition) ;
do-while语句为:
do
{
...
} while (condition);
6.6 Switch 语句
switch语句为:
switch (condition)
{
case A:
...
break;
case B:
...
break;
default:
...
break;
}
6.7 Try-catch 语句
try-catch语句为:
try
{
...
}
catch (Exception)
{
}
或者
try
{
...
}
catch (Exception e)
{
...
}
或者
try
{
...
}
catch (Exception e)
{
...
}
finally
{
...
}
7 空格
7.1空行
空行增加了可读性。空行把代码按内在相关逻辑分成块。两个空行常用于:
l 源文件的逻辑区块之间
l 类与接口的定义之间(用每个文件定义一个类或者借口来避免这种情况)
一个空行常用于:
l 方法之间
l 属性之间
l 方法中的局部变量和该局部变量的第一个语句之间
l 方法中的逻辑模块之间,以增加可读性
注意:任何时候都应该保持正确的缩进。缩进比空行更能够增加可读性。保持正确的缩进,在VS2003中,只要选中所有的代码,按CTRL+KF即可完成。
7.2代码中的空格
逗号或者分号后面都应该有空格,例如:
TestMethod(a, b, c);
不要使用TestMethod(a,b,c)或者TestMethod( a, b, c );
运算符前后都要有空格(除了一元运算符,如取补集或者是逻辑非),例如:
a = b; // 不要使用a=b;
for (int i = 0; i < 10; ++i)
不要使用for (int i=0; i<10; ++i)或者for(int i=0;i<10;++i)
8 命名规范
8.1大写规范
8.1.1 Pascal casing
这个规范要求大写每个单词的首字母(如TestCounter)。
8.1.2 Camel casing
这个规范要求大写每个单词的首字母除了第一个单词,如testCounter。
8.1.3 Upper case
仅仅对于由一个或两个字母的缩写组成的标识符,我们用upper case,那些由3个或更多字符组成的标识符应该用Pascal Casing.例如:
public class Math
{
public const PI = ...;
public const E = ...;
public const FeigenBaumNumber = ...;
}
8.2命名准则
一般说来,在名字里使用下划线,并且根据匈牙利的符号准则来命名都被视为不实用。匈牙利的符号是一组前后缀,用在变量名称里表示变量类型的。在早先的Windows程序语言里面,就是广泛的使用这种命名方式的,但是现在已经舍弃了这种命名方式,或者说,至少大多数人反对使用它。在这种新的准则下是不可以使用匈牙利符号的。而且要记住:一个好的变量名称描述的是变量的含义而非类型。这种准则的一个反例就是GUI编码。包含GUI元素的所有的域和变量的名称都加上了不缩写的类型名当后缀。例如:
System.Windows.Forms.Button cancelButton;
System.Windows.Forms.TextBox nameTextBox;
8.2.1 Namespace
l Namespace的命名规则一般如下:CompanyName.TechnologyName[.Feature][.Design]。(例如:Microsoft.Media、Microsoft.Media.Design)
l 使用一个稳定的,易识别的技术名称作为第二个名字。如果需要为一个namespace定义设计时期的类型,那么需要重新定义一个namespace包含这些类型,其名称就是在前一个namespace后加上.Design。例如:System.Windows.Forms.Design Namespace 包含了设计器及相关的类用来设计System.Windows.Forms中的类。
l 使用Pascal大写方式,用点号分隔逻辑成分。(例如,Microsoft.Office.PowerPoint)。如果你的品牌使用的是非传统大写方式,那么一定要遵循你的品牌所确定使用的大写方式。即使这种方式背离了通常的名称空间大写规则。(例如,NeXT.WebObjects, and ee.cummings.)
l 该用复数的时候要使用复数的名称空间名。例如,使用System.Collections而不是使用System.Collection。本规则的特例是品牌名称和缩写。例如:使用use System.IO 而不是System.IOs
l namespace和类不能使用同样的名字。例如,有一个namespace被命名为Debug后,就不要再使用Debug作为类名。
l namespace没有必要一定和assembly的名称相同。比如assembly MyCompany.MyTechnology.dll并不意味着其中一定包含着MyCompany.MyTechnology namespace
8.2.2类的命名准则
l 类名必须是名词或是名词短语
l 使用Pascal Casing (见8.1.1)
l 不要使用任何类型前缀(比如C)。例如用FileStream而不是CFileStream来命名一个类
l 尽可能少在类名中使用缩写
l 不要使用连字号“_”
l 在必须的情况下,一个类名的开始字母可以为I,即使这个类不是Interface。比如类名是IdentifyStore
l 在例外中,必须将Exception作为类的结尾。例如: ApplicationException
l 在属性的后面加上Attribute后缀,来自定义Attribute类
8.2.3接口命名准则
l 使用名词,名词短语,或者是表示行为的形容词来命名接口。(例如:IComponent 或者IEnumberable)
l 使用Pascal Casing (见8.1.1)
l 名称前加I作为前缀,I后跟一个大写字母(接口名称的第一个字母)
l 尽可能少在接口名中使用缩写
l 不要使用连字号“_”
l 当类完成一个接口时,定义这一对类/接口组合就要使用相似的名称。两个名称的不同之处只是接口名前有一个I前缀
下面我们举个例子,来看看接口IComponent和它的标准执行,类Component。
public interface IComponent
{
}
public class Component : IComponent
{
}
8.2.4枚举变量的命名准则
l 对于枚举和枚举类型的变量的名称使用Pascal Casing
l 不给枚举类型或枚举变量添加前缀(或后缀)
l 尽可能少在枚举中使用缩写
l 枚举名称需使用单数名词。在特殊情况下可以使用复数
8.2.5只读和常量的命名
l 使用名词,名词短语,或者名词缩写来命名static fields
l 使用Pascal Casing (见8.1.1)
8.2.6成员变量的命名
l 使用描述性的名称,该名称可以确定变量含义和类型,推荐以变量含义为基础的名称。
l 使用Pascal Casing (见8.1.1)
l 名称前加f作为前缀,f后跟一个大写字母。(例如:fLength)
l 不要使用匈牙利命名
8.2.7参数
l 使用描述性的名称,该名称可以确定变量含义和类型。推荐以参数含义为基础的名称。
l 使用Camel Casing (见8.1.2)
l 根据参数的意思来命名参数,而不是根据参数的类型来命名。我们希望开发工具可以用很方便的方式提供关于参数种类的信息,这样参数名可以得到更好的使用,可以对语义而不是对类型进行描述。但是偶尔使用根据类型命名的参数名也是完全可以的
l 不要用匈牙利类型的符号作为参数名的前缀
8.2.8变量命名
l 对于“平凡”的记数循环,记数变量推荐用I,j,k,l,m,n(见10.2关于全局变量更明智的命名方式的示例等等)
l 推荐对于类似Is,Hasor Can等boolean变量使用前缀,一般给boolean变量可以意味着对错的名称(例如:fileFound, done, success或者加前缀: isFileFound, isDone, isSuccess 但是不要使用IsName这种毫无意义的名称)
l 使用Camel Casing (见8.1.2)
8.2.9 Method的命名
l 用动词或者动词短语给method来命名
l 用Pascal Casing (见8.1.2)
8.2.10属性的命名
l 用名词或名词短语命名属性
l 用Pascal Casing (见8.1.2)
l 可考虑用属性的类型名来命名它
l 不要使用匈牙利命名
8.2.11事件的命名
l 在delegate定义时,用EventHandler作为后缀
l 使用名为sender和e的两个参数。sender参数代表发出这个事件的对象。sender参数永远是一个类型对象,即使它可能使用了更为特定的类型。与事件相关的状态被封装在一个名为e的事件类型中。
l 使用Pascal Casing (见8.1.1)
l 在定义事件参数类型的时候,需要加上EventArgs后缀
l 使用现在进行时来命名一个即将被执行的事件,用过去时表示一个执行后的事件。例如,用Closing和Closed来命名事件。不要用BeforeXxx/AfterXxx来命名事件
l 不要给事件名加前缀和后缀。例如,用Close而不是OnClose
l 可考虑用动词或者动词短语定义事件名称
l 通常情况下,需要给一个protected virtual的方法用来调用Event,这个方法用OnXxx命名,这样子类就可以对这个方法进行override。这个方法有且仅有一个参数e,而sender通常用this来赋值
8.2.12 大小写总结
Type |
Case |
Notes |
Class / Struct |
Pascal Casing |
|
Interface |
Pascal Casing |
以I开始 |
Enum values |
Pascal Casing |
|
Enum type |
Pascal Casing |
|
Events |
Pascal Casing |
|
Exception class |
Pascal Casing |
以Exception结束 |
Attribute class |
Pascal Casing |
以Attribute结束 |
public Fields |
Pascal Casing |
很少使用,通常用属性 |
Methods |
Pascal Casing |
|
Namespace |
Pascal Casing |
|
Property |
Pascal Casing |
|
Field |
Pascal Casing |
以f开始 |
Type |
Case |
Notes |
Parameters |
Camel Casing |
|
9程序设计惯例
9.1可见度
任何时候都不要将实例变量声明为public,要使用private关键字。尽量使用属性或者使用public const或者public static readonly来替代。
9.2常量
除了0、-1、1等常用数值外,使用的数值都要声明为常量(例如,应用程序可以处理3540个用户。如果“3540”采用硬编码的方式分散在25000行代码中的50行中,总共427处。如果现在改为3800个,所有的值都要修改,很难保证每个值都能被正确的修改。)。
不要使用魔法数字,例如:把常数数值直接放入源代码。过后如果变化(比如,应用程序现在可以处理3540个用户而不是分散于25000LOC的50行编码中的427个hardcoded)易出错的和徒然的,则这些数值要被更改。我们用下面这种方式来定义有常数值的常量:
public class MyMath
{
public const double PI = 3.14159...
}
10编码实例
10.1大括号的实例
namespace ShowMeTheBracket
{
public enum Test
{
TestMe,
TestYou
}
public class TestMeClass
{
Test test;
public Test Test
{
get
{
return test;
}
set
{
test = value;
}
}
void DoSomething()
{
if (test == Test.TestMe)
{
//...stuff gets done
}
else
{
//...other stuff gets done
}
}
}
}
括号应该另起一行。
10.2变量命名示例
for (int i = 1; i < num; ++i)
{
meetsCriteria[i] = true;
}
for (int i = 2; i < num / 2; ++i)
{
int j = i + i;
while (j <= num)
{
meetsCriteria[j] = false;
j += i;
}
}
for (int i = 0; i < num; ++i)
{
if (meetsCriteria[i])
{
Console.WriteLine(i + " meets criteria");
}
}
用智能命名代替上面的:
for (int primeCandidate = 1; primeCandidate < num; ++primeCandidate)
{
isPrime[primeCandidate] = true;
}
for (int factor = 2; factor < num / 2; ++factor)
{
int factorableNumber = factor + factor;
while (factorableNumber <= num)
{
isPrime[factorableNumber] = false;
factorableNumber += factor;
}
}
for (int primeCandidate = 0; primeCandidate < num; ++primeCandidate)
{
if (isPrime[primeCandidate])
{
Console.WriteLine(primeCandidate + " is prime.");
}
}
注:Indexer 变量一般用i, j, k等等。但是在上面这种情况下,重新考虑这种原则还是有意义的。总之,当同一个counter或者indexer被重复使用的时候,要给它们一个有意义的名称。