陷阱六.虚方法必须被显式重载
在C#中,如果程序员决定重载一个虚方法,他(她)必须显式使用override关键字。
让我们考察一下这样做的好处。假定公司A写了一个Window类,公司B购买了公司A的Window类的一个拷贝作为基类。公司B的程序员从中派生【译注:原文为...using...,从下文来看,显然是“派生”之意。事实上,使用类的方式还有“组合”(也有说为“嵌入”或“包容”(COM语义)等等),后者不存在下文所描述的问题】出ListBox类和RadioButton类。公司B的程序员不知道或不能控制Window类的设计,包括公司A将来对Window类可能做的修改。
现在假定公司B的程序员决定为ListBox类加入一个Sort方法:
public class ListBox : Window
{
public virtual void Sort() {}
}
这是没有问题的—直到公司A的Window类作者发布了Window类的版本2,公司A的程序员向Window类也加入了一个public的Sort方法:
public class Window
{
public virtual void Sort() {}
}
在C++中,Window类新的虚方法Sort将会作为ListBox虚方法的基类方法。当你试图调用Window的Sort时,实际上调用的是ListBox的Sort。C#中虚方法【译注:原文写成virtual function】永远被认为是虚拟调度的根。这就是说,只要C#找到了一个虚方法,它就不会再沿着继承层次进一步寻找了,如果一个新的Sort虚方法被引入Window,ListBox的运行时行为不会被改变。当ListBox再次被编译时,编译器会发出如下警告:
"\class1.cs(54,24): warning CS0114: 'ListBox.Sort()' hides inherited member 'Window.Sort()'.
如果要使当前成员重载实现,可加入override关键字。否则,加上new关键字。
如果想要移去这个警告,程序员必须明确指明他的意图。可以将ListBox的Sort方法标为new,以指明它不是对Window的虚方法的重载:
public class ListBox : Window
{
public new virtual void Sort() {}
}
这样编译器就不会再警告。另一方面,如果程序员想重载Window的方法,只要显式加上override关键字即可。
陷阱七:不可以在头部进行初始化
C#里的初始化不同于C++。假定你有一个类Person,它有一个私有成员变量age;一个派生类Employee,它有一个私有成员变量salaryLeverl。在C++中,你可以在Employee构造器的成员初始化列表部分初始化salaryLevel:
Employee::Employee(int theAge, int theSalaryLevel):
Person(theAge) // 初始化基类
salaryLevel(theSalaryLevel) // 初始化成员变量
{
// 构造器体
}
在C#中,这个构造器是非法的。尽管你仍可以如此初始化基类,但对成员变量的初始化将导致一个编译时错误。你可以在成员变量声明处对其赋初始值:
Class Employee : public Person
{
// 在这儿声明
private salaryLevel = 3; //初始化
}
【译注:以上代码有误LC#中,正确写法如下:
class Employee: Person
{
private int salaryLevel = 3;
}
】
你不需要在每一个类声明的后面都加上一个分号。每一个成员都必须要有显式的访问级别声明。
陷阱8.不能把布尔值转换为整型值
在C#中,布尔值(true、false)不同于整型值。因此,不能这么写:
if ( someFuncWhichReturnsAValue() )//【译注:假定这个方法不返回布尔值】
也不能指望如果someFuncWhichReturnsAValue返回一个0它将等于false,否则为true。一个好消息是误用赋值操作符而不是相等操作符的老毛病不会再犯了。因此,如果这么写:
if ( x = 5 )
将会得到一个编译时错误,因为x = 5的结果为5,而它不是布尔值。
【译注:以下是C++里一不小心会犯的逻辑错误,编译器不会有任何提示L运行得很顺畅,不过结果并不是你想要的:
C++:
#include "stdafx.h"
int main(int argc, char* argv[])
{
int n = 0;
if (n = 1)//编译器啥都没说L一般推荐写为1 == n,万一写成1 = n编译器都不同意J
{
printf("1\n");
}
else
{
printf("0\n");
}
return 0;
}
以上运行结果为1,这未必是你想要的。
C#:
using System;
public class RyTestBoolApp
{
public static void Main()
{
int n = 0;
if (n = 1)//编译器不同意J无法将int转换成bool
{
Console.WriteLine("1");
}
else
{
Console.WriteLine("0");
}
}
}
但如果是这种情况:
bool b = false;
if (b = true)
...
不管是C++还是C#都没招L
】
【译注:C++程序员一般是喜欢这种自由的写法:
if (MyRef)
if (MyInt)
但在C#里,必须写成:
if (MyRef != null)//或if (null != MyRef)
if (MyInt != 0)//或if (0 != MyInt)
等。
】
陷阱九.switch语句不可“贯穿”【译注:即fall through,Beta2的联机文档就是如此译法】
在C#中,如果在case语句里有代码的话,那它就不可“贯穿”到下一句。因此,尽管下面代码在C++里合法,但在C#中则不然:
switch (i)
{
case 4:
CallFuncOne();
case 5: // 错误,不可以“贯穿”
CallSomeFunc();
}
为了达到这个目的,需要显式地使用goto语句:
switch (i)
{
case 4:
CallFuncOne();
goto case 5;
case 5:
CallSomeFunc();
}
如果case语句没做任何事(里面没有代码)就可以“贯穿”:
switch (i)
{
case 4: // 可以“贯穿”
case 5: