本节介绍编写C#程序时应注意的规则。
本节将讨论变量、类、方法等的命名规则。注意本节所介绍的规则不仅是规则,也是C#编译器强制使用的。
标识符是给变量、用户定义的类型(例如类和结构)和这些类型的成员指定的名称。标识符区分大小写,所以interestRate 和 InterestRate是不同的变量。确定在C#中可以使用什么标识符有两个规则:
● 它们必须以一个字母或下划线开头,但可以包含数字字符;
● 不能把C#关键字用作标识符。
C#包含如表2-11所示的保留关键字。
表 2-11
abstract |
do |
In |
protected |
true |
as |
double |
Int |
public |
try |
base |
else |
Interface |
readonly |
typeof |
bool |
enum |
Internal |
ref |
uint |
break |
event |
Is |
return |
ulong |
byte |
explicit |
lock |
sbyte |
unchecked |
case |
extern |
long |
sealed |
unsafe |
catch |
false |
namespace |
short |
ushort |
char |
finally |
new |
sizeof |
using |
checked |
fixed |
null |
stackalloc |
virtual |
class |
float |
object |
static |
volatile |
const |
for |
operator |
string |
void |
continue |
foreach |
out |
struct |
while |
decimal |
goto |
override |
switch |
|
default |
if |
params |
this |
|
delegate |
Implicit |
private |
throw |
|
如果需要把某一保留字用作标识符(例如,访问一个用另一种语言编写的类),可以在标识符的前面加上前缀@符号,指示编译器其后的内容是一个标识符,而不是C#关键字(所以abstract不是有效的标识符,而@abstract是)。
最后,标识符也可以包含Unicode字符,用语法/uXXXX来指定,其中XXXX是Unicode字符的四位16进制代码。下面是有效标识符的一些例子:
● Name
● überfluß
● _Identifier
● /u005fIdentifier
最后两个标识符是相同的,可以互换(005f是下划线字符的Unicode代码),所以在相同的作用域内不要声明两次。注意虽然从语法上看,标识符中可以使用下划线字符,但在大多数情况下,最好不要这么做,因为它不符合Microsoft的变量命名规则,这种命名规则可以确保开发人员使用相同的命名规则,易于阅读每个人编写的代码。
在任何开发环境中,通常有一些传统的编程风格。这些风格不是语言的一部分,而是约定,例如,变量如何命名,类、方法或函数如何使用等。如果使用某语言的大多数开发人员都遵循相同的约定,不同的开发人员就很容易理解彼此的代码,有助于程序的维护。例如,Visual Basic 6的一个公共(但不统一)约定是,表示字符串的变量名以小写字母s或str开头,如Dim sResult As String或 Dim strMessage As String。约定主要取决于语言和环境。例如,在Windows平台上编程的C++开发人员一般使用前缀psz或 lpsz表示字符串:char *pszResult; char *lpszMessage;,但在UNIX机器上,则不使用任何前缀:char *Result; char *Message;。
从本书中的示例代码中可以总结出,C#中的约定是命名变量时不使用任何前缀:string Result; string Message;。
注意:
用带有前缀字母的变量名来表示某个数据类型,这种约定称为Hungarian表示法。这样,其他阅读该代码的开发人员就可以立即从变量名中了解它代表什么数据类型。在有了智能编辑器和IntelliSense之后,人们普遍认为Hungarian表示法是多余的。
但是,在许多语言中,用法约定是从语言的使用过程中逐渐演变而来的,Microsoft编写的C#和整个.NET Framework都有非常多的用法约定,详见.NET/C# MSDN文档说明。这说明,从一开始,.NET程序就有非常高的互操作性,开发人员可以以此来理解代码。用法规则还得益于20年来面向对象编程的发展,因此相关的新闻组已经仔细考虑了这些用法规则,而且已经为开发团体所接受。所以我们应遵守这些约定。
但要注意,这些规则与语言规范是不同的。用户应尽可能遵循这些规则。但如果有很好的理由不遵循它们,也不会有什么问题。例如,不遵循这些用法约定,也不会出现编译错误。一般情况下,如果不遵循用法规则,就必须有一个说得过去的理由。规则应是一个正确的决策,而不是让人头痛的东西。在阅读本书的后续内容时,应注意到在本书的许多示例中,都没有遵循该约定,这通常是因为某些规则适用于大型程序,而不适合于本书中的小示例。如果编写一个完整的软件包,就应遵循这些规则,但它们并不适合于只有20行代码的独立程序。在许多情况下,遵循约定会使这些示例难以理解。
编程风格的规则非常多。这里只介绍一些比较重要的规则,以及最适合于用户的规则。如果用户要让代码完全遵循用法规则,就需要参考MSDN文档说明。
使程序易于理解的一个重要方面是给对象选择命名的方式,包括变量名、方法名、类名、枚举名和命名空间的名称。
显然,这些名称应反映对象的功能,且不与其他名称冲突。在.NET Framework中,一般规则也是变量名要反映变量实例的功能,而不是反映数据类型。例如,Height就是一个比较好的变量名,而IntegerValue就不太好。但是,这种规则是一种理想状态,很难达到。在处理控件时,大多数情况下使用ConfirmationDialog 和 ChooseEmployeeListBox等变量名比较好,这些变量名说明了变量的数据类型。
名称的约定包括以下几个方面:
(1) 名称的大小写
在许多情况下,名称都应使用Pascal大小写命名形式。 Pascal 大小写形式是指名称中单词的第一个字母大写: EmployeeSalary, ConfirmationDialog, PlainTextEncoding。注意,命名空间、类、以及基类中的成员等的名称都应遵循该规则,最好不要使用带有下划线字符的单词,即名称不应是employee_salary。其他语言中常量的名称常常全部都是大写,但在C#中最好不要这样,因为这种名称很难阅读,而应全部使用Pascal 大小写形式的命名约定:
const int MaximumLength;
我们还推荐使用另一种大小写模式:camel大小写形式。这种形式类似于Pascal 大小写形式,但名称中第一个单词的第一个字母不是大写:employeeSalary、confirmationDialog、plainTextEncoding。有三种情况可以使用camel大小写形式。
● 类型中所有私有成员字段的名称都应是camel大小写形式:
public int subscriberId;
但要注意成员字段名常常用一个下划线开头:
public int _subscriberId;
● 传递给方法的所有参数都应是camel大小写形式:
public void RecordSale(string salesmanName, int quantity);
● camel大小写形式也可以用于区分同名的两个对象—— 比较常见的情况是属性封装一个字段:
private string employeeName;
public string EmployeeName
{
get
{
return employeeName;
}
}
如果这么做,则私有成员总是使用camel大小写形式,而公共的或受保护的成员总是使用Pascal 大小写形式,这样使用这段代码的其他类就只能使用Pascal 大小写形式的名称了(除了参数名以外)。
还要注意大小写问题。C#是区分大小写的,所以在C#中,仅大小写不同的名称在语法上是正确的,如上面的例子。但是,程序集可能在VB .NET应用程序中调用,而VB .NET是不区分大小写的,如果使用仅大小写不同的名称,就必须使这两个名称不能在程序集的外部访问。(上例是可行的,因为仅私有变量使用了camel大小写形式的名称)。否则,VB .NET中的其他代码就不能正确使用这个程序集。
(2) 名称的风格
名称的风格应保持一致。例如,如果类中的一个方法叫ShowConfirmationDialog(),其他方法就不能叫ShowDialogWarning()或 WarningDialogShow(),而应是ShowWarningDialog()。
(3) 命名空间的名称
命名空间的名称非常重要,一定要仔细设计,以避免一个命名空间中对象的名称与其他对象同名。记住,命名空间的名称是.NET区分共享程序集中对象名的惟一方式。如果软件包的命名空间使用的名称与另一个软件包相同,而这两个软件包都安装在一台计算机上,就会出问题。因此,最好用自己的公司名创建顶级的命名空间,再嵌套后面技术范围较窄、用户所在小组或部门、或类所在软件包的命名空间。Microsoft建议使用如下的命名空间:<CompanyName>. <TechnologyName>,例如:
WeaponsOfDestructionCorp.RayGunControllers
WeaponsOfDestructionCorp.Viruses
(4) 名称和关键字
名称不应与任何关键字冲突,这是非常重要的。实际上,如果在代码中,试图给某个对象指定与C#关键字同名的名称,就会出现语法错误,因为编译器会假定该名称表示一个语句。但是,由于类可能由其他语言编写的代码访问,所以不能使用其他.NET语言中的关键字作为对象的名称。一般说来,C++关键字类似于C#关键字,不太可能与C++混淆,Visual C++常用的关键字则用两个下划线字符开头。与C#一样,C++关键字都是小写字母,如果要遵循公共类和成员使用Pascal风格的名称的约定,则在它们的名称中至少有一个字母是大写,因此不会与C++关键字冲突。另一方面,VB的问题会多一些,因为VB的关键字要比C#的多,而且它不区分大小写,不能依赖于Pascal风格的名称来区分类和成员。
表2-12列出了VB中的关键字和标准函数调用,无论对C#公共类使用什么大小写组合,这些名称都不应使用。
表 2-12
|
|
|
|||
Abs | |||||
Do |
Loc |
RGB |
|
||
Add |
Double |
Local |
Right |
|
|
AddHandler |
Each |
Lock |
RmDir |
|
|
AddressOf |
Else |
LOF |
Rnd |
|
|
And |
Empty |
Long |
SaveSettings |
|
|
Ansi |
End |
Loop |
Second |
|
|
AppActivate |
Enum |
LTrim |
Seek |
|
|
Append |
EOF |
Me |
Select |
|
|
As |
Erase |
Mid |
SetAttr |
|
|
Asc |
Err |
Minute |
SetException |
|
|
Assembly |
Error |
MIRR |
Shared |
|
|
Atan |
Event |
MkDir |
Shell |
|
(续表)
Auto |
Exit |
Module |
Short |
Beep |
Exp |
Month |
Sign |
Binary |
Explicit |
MustInherit |
Sin |
BitAnd |
ExternalSource |
MustOverride |
Single |
BitNot |
False |
MyBase |
SLN |
BitOr |
FileAttr |
MyClass |
Space |
BitXor |
FileCopy |
Namespace |
Spc |
Boolean |
FileDateTime |
New |
Split |
ByRef |
FileLen |
Next |
Sqrt |
Byte |
Filter |
Not |
Static |
ByVal |
Finally |
Nothing |
Step |
Call |
Fix |
NotInheritable |
Stop |
Case |
For |
NotOverridable |
Str |
Catch |
Format |
Now |
StrComp |
CBool |
FreeFile |
NPer |
StrConv |
CByte |
Friend |
NPV |
Strict |
CDate |
Function |
Null |
String |
CDbl |
FV |
Object |
Structure |
CDec |
Get |
Oct |
Sub |
ChDir |
GetAllSettings |
Off |
Switch |
ChDrive |
GetAttr |
On |
SYD |
Choose |
GetException |
Open |
SyncLock |
Chr |
GetObject |
Option |
Tab |
CInt |
GetSetting |
Optional |
Tan |
Class |
GetType |
Or |
Text |
Clear |
GoTo |
Overloads |
Then |
CLng |
Handles |
Overridable |
Throw |
Collection |
Hour |
ParamArray |
Timer |
Command |
If |
Pmt |
TimeSerial |
Compare |
Iif |
PPmt |
TimeValue |
Const |
Implements |
Preserve |
To |
Cos |
Imports |
|
Today |
CreateObject |
In |
Private |
Trim |
CShort |
Inherits |
Property |
Try |
CSng |
Input |
Public |
TypeName |
CStr |
InStr |
Put |
TypeOf |
(续表)
CurDir |
Int |
PV |
UBound |
Date |
Integer |
QBColor |
UCase |
DateAdd |
Interface |
Raise |
Unicode |
DateDiff |
Ipmt |
RaiseEvent |
Unlock |
DatePart |
IRR |
Randomize |
Until |
DateSerial |
Is |
Rate |
Val |
DateValue |
IsArray |
Read |
Weekday |
Day |
IsDate |
ReadOnly |
While |
DDB |
IsDbNull |
ReDim |
Width |
Decimal |
IsNumeric |
Remove |
With |
Declare |
Item |
RemoveHandler |
WithEvents |
Default |
Kill |
Rename |
Write |
Delegate |
Lcase |
Replace |
WriteOnly |
DeleteSetting |
Left |
Reset |
Xor |
Dim |
Lib |
Resume |
Year |
Dir |
Line |
Return |
|
类中出现混乱的一个方面是一个数是用属性还是方法来表示。这没有硬性规定,但一般情况下,如果该对象的外观和操作都像一个变量,就应使用属性来表示它(属性详见第3章),即:
● 客户机代码应能读取它的值,最好不要使用只写属性,例如,应使用SetPassword()方法,而不是Password只写属性。
● 读取该值不应花太长的时间。实际上,如果它是一个属性,通常表示读取过程花的时间相对较短。
● 读取该值不应有任何不希望的负面效应。设置属性的值,不应有与该属性不直接相关的负面效应。设置对话框的宽度会改变该对话框在屏幕上的外观,这是可以的,因为它与属性是相关的。
● 应可以用任何顺序设置属性。在设置属性时,最好不要因为还没有设置另一个相关的属性而抛出一个异常。例如,如果为了使用访问数据库的类,需要设置ConnectionString、UserName和Password,应确保了已经执行了该类,这样用户才能按照任何顺序设置它们。
● 顺序读取属性也应有相同的效果。如果属性的值可能会出现预料不到的改变,就应把它编写为一个方法。在监视汽车运动的类中,把speed编写为属性就不是一种好的方式,而应使用GetSpeed(),另一方面,应把Weight 和EngineSize编写为属性,因为对于给定的对象,它们是不会改变的。
如果要编码的对象满足上述所有条件,就应对它使用属性,否则就应使用方法。
字段的用法非常简单。字段应总是私有的,但在某些情况下也可以把常量或只读字段设置为公有,原因是如果把字段设置为公有,就可以在以后扩展或修改类。
遵循上面的规则就可以编写出好的代码,而且这些规则应与面向对编程的风格一起使用。
Microsoft在保持一致性方面相当谨慎,在编写.NET基类时就可以遵循它自己的规则。在编写.NET代码时应很好地遵循这些规则,对于基类来说,就是类、成员、命名空间的命名方式和类层次结构的工作方式等,如果编写代码的风格与基类的编写风格相同,就不会犯什么错误。