C# 2.0对现有语法的改进
——lover_P 于北京工业大学1号楼221寝室
[修订说明]
2004-08-24
第一次修订。修改了大量的错别字和文法错误。添加了对(文中C#指代的是C#1.2及以前的版本,而C#2.0指代的是微软尚未正式推出的C# Whidbey;文章中的所有代码均在版本号为8.00.30703.4的C#编译器下进行了测试,标有*的错误消息得自版本号为7.10.3052.4的C#编译器。)
[内容]
静态类 属性的可访问性限定 命名空间别名 编译器指令 固定大小缓冲区 参考文献
静态类 使用C#进行.NET框架程序设计的人应该都知道,无法将一个类声明为静态的。例如,下面的类声明:
static int i;
}
在C#中是无效的,当我们尝试编译这段代码时会得到下面的编译错误*:
由于无法用
int i;
}
则编译器会报告错误:
同时,如果我们声明该类的变量或是试图建立该类的一个实例时,如下面的代码:
A a; // error CS0723
void Foo() {
a = new A(); // error CS0712
}
}
则会得到下面的两个编译错误:
error CS0712: Cannot create an instance of the static class 'A'
很显然,C#2.0中对静态类的支持极大程度地避免了我们在书写程序中的意外失误,尤其是加强了静态类的逻辑性,提高了代码的可读性。
属性的可访问性限定 C#为我们提供了相当方便的属性定义,使得我们可以像访问类的公有变量成员那样访问类的属性,但还可以同时得到像访问函数那样的安全性。然而,C#只允许属性的设置动作(
int _intValue; // 与属性相关的一个int类型的成员变量
// 公有且只读的属性,允许任何类获取该属性的值:
public int Value {
get { return _intValue; }
}
// 下面的方法需要设置上面的属性,
// 但只能通过访问私有成员变量来完成,
// 并且要另外进行错误处理
private void SomeMethod() {
int i;
// ......
// 下面的if-else语句仅用来设置属性值:
if(0 < i && i < 10) {
_intValue = i;
}
else {
// 错误处理
}
}
}
很明显,这种做法是非常麻烦的。如果在多个地方改变了成员变量的值会使代码变得冗长不可读,还很有可能会产生错误,譬如该类有另外一个方法:
int i;
// ......
// 下面的if-else语句仅用于设置属性值,
// 但其对i的区间检测发生了错误
if(0 < i && i <= 10) { // 注意这里的 <= 运算符
_intValue = i;
}
// 并且没有进行错误处理
// ......
}
上面的方法对将要赋给私有变量成员的值的检查区间是错误的,这种错误是很有可能发生的。一旦调用了这个方法,
// 下面的内部方法对上面的属性进行设置工作
internal void _setIntValue(int newValue) {
if(0 < newValue && newValue < 10) {
_intValue = newValue;
}
else {
throw new System.InvalidArgumentException (
“The new value must greater than 0 and less than 10”
);
}
}
// 下面的方法需要对上述属性进行设置
private void SomeMethod() {
int i;
// ......
_setIntValue(i); // 通过调用内部方法进行
}
这样做虽然避免了逻辑错误的出现(至少使出现了错误时的解决工作变得容易),但其可读性仍然不理想,尤其是逻辑性很差,与“属性”本身的意义相去甚远。
然而C#2.0允许我们对属性的
int _intValue; // 与属性相关的一个int类型的成员变量
// 公有的属性,
// 允许任何类获取该属性的值,
// 但只有程序集内部的类和该类中的私有成员
// 能够设置属性的值
public int Value {
get {
return _intValue;
}
internal set {
if(0 < value && value < 10) {
_intValue = value;
}
else {
throw new System.InvalidArgumentException (
“The new value must greater than 0 and less than 10”
);
}
}
} // property
// 下面的方法需要对上述属性进行设置
private void SomeMethod() {
int i;
// ......
Value = i;
}
}
尤其在程序集中的其他类的成员中访问该属性时相当方便:
public class B {
public A SomeMethod() {
A a = new A();
a.Value = 8; // 这里对属性进行设置,方便!
return a;
}
}
可以看出,能够对属性的获取和设置操作分别设置可访问性限定极大地增强了C#程序的可读性和语言逻辑性,写出的程序也具有更强的可维护性。
命名空间别名 在C#中,使用类(如声明成员变量或调用静态方法等)的时候需要指定类的完全名称,即命名空间前缀加类的名字。如果我们要在控制台上打印“Hello, world!”,则需要写:
其中,
// ......
public class Test {
public static void Main() {
Console.WriteLine(“Hello, world!”);
}
}
呵呵,这不就是经典的“Hello World”范例么?很显然,这种写法方便得多,可以极大地提高开发效率。
然而,两个命名空间中很可能具有同名的类,而我们恰好需要用到这些同名的类。这种情况会经常发生。譬如在.net框架类库中就存在有三个Timer类:
using System.Windows.Forms;
// ......
public class MainForm : Form {
System.Timer.Timer CheckTimer;
System.Windows.Forms.Timer AnimateTimer;
// ......
}
这样的程序仍然显得冗长。
在C#2.0中,,即可为命名空间
using WinForm = System.Windows.Forms;
// ......
public class MainForm : WinForm.Form {
SysTimer.Timer CheckTimer; // 与命名空间的使用完全相同
WinForm.Timer AnimateTimer;
// ......
}
我们可以看到,这样的代码要简洁得多。
namespace MyNamespace {
namespace System {
public class MyConsole {
public void WriteLine(String str) {
System.Console.WriteLine(str); // 注意这一行!
}
}
}
}
这里我在自己的命名空间内声明了一个
然而,C#2.0引入了 图示1:global关键字的地位
在C#1.x中:
|
+- System (namespace)
| |
| +- Console (class)
| +WriteLine (method)
| +- Int32 (struct)
| +- ...
|
+- MyNameSpace (namespace)
|
+- System ([sub]namespace)
+ MyConsole (class)
在C#2.0中
|
+- System (namespace)
| |
| +- Console (class)
| +WriteLine (method)
| +- Int32 (struct)
| +- ...
|
+- MyNameSpace (namespace)
|
+- System ([sub]namespace)
+ MyConsole (class)
这样一来,我们就能通过使用
namespace MyNamespace {
namespace System {
public class MyConsole {
public void WriteLine(String str) {
global.System.Console.WriteLine(str); // 注意这一行!
}
}
}
}
编译器指令 在我们调试C#程序时,经常会声明一些临时变量用来监测程序状态,并在调试完成后将这些声明删除。而当我们声明了这样的临时变量,在调试过程中却没有用到的时候,我们通常会得到大量的如:
的警告。然而,我们很清楚这样的警告是无害的。同样,很多其他时候我们也会得到一些警告,但我们不得不从大量的无害的警告中寻找我们需要的错误消息。
然而,C#2.0为我们提供了一条新的编译器指令:,使得我们能够在一段代码中禁止一些我们确认无害的警告(通过指定警告的编号)。以前,这种工作只能由特定的编译器选项(譬如Microsoft Visual C#编译器的指令来命令编译器仅仅隐藏某一个代码块中相应的警告。我们可以用下列代码来禁止产生上面的例子中所述的“未使用参数”的警告:
public void SomeMethod() {
// 下面的编译器指令禁止了“未使用参数”的警告:
#pragma warning disable 0168
int tempStatus;
// ......
// 下面的编译器指令重新允许产生“未使用参数”的警告:
#pragma warning restore 0168
}
}
这样,当编译器编译指令重新打开了该警告。
固定大小缓冲区 最后,除了上述的一些特性外,C#2.0还提供了“固定大小缓冲区(Fixed Size Buffers)”的新特性。即像C语言那样可以在结构中声明一个固定大小的数组,这通过
public fixed char Buffer[128];
}
但由于我所使用的编译器尚未支持这一特性,手头又没有相应的资料,在此就不做介绍了。
以上是我对C#2.0中除了泛型、迭代器、匿名方法和分部类型等重大改进之外的一些对现有特性进行的改进的简要介绍。这些改进看起来很细微,却极大程度地增强了C#语言的逻辑性,使得我们能够写出更加漂亮且可维护性更强的代码。我的介绍是非常简略的,甚至可能有错误,希望大家指教。(联系方式:lyb_dotNET@hotmail.com)
参考文献 [1]《Visual C# Whidbey: Language Ehancements》,Anders Hejlsberg在Microsoft Professional Developers Conference 2003(2003.10, Los Angeles, CA)上的演讲及其PowerPoint文档。