`
mmdev
  • 浏览: 13149875 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

[原创]C# 2.0对现有语法的改进

阅读更多

C# 2.0对现有语法的改进

作者:lover_P


[自序]

尽管Microsoft Visual Studio .NET 2005(过去好像叫Visual Studio .NET 2004)一再推迟其发布日期,但广大开发者对其的猜测以及各种媒体对其各方面的“曝光”也似乎已经充斥了网络。但与C#有关的文章似乎无外乎两个方面:VS.NET 2005 IDE特性、介绍C# 2.0中引入的“四大特性(泛型、匿名方法、迭代器和不完整类型)”。对IDE的研究我不想谈了,微软就是微软,他的窗口应用程序总是没得说的。而就语言本身的改进来说,在我听完了Anders Hejlsberg在Microsoft Professional Developers Conference 2003(2003.10, Los Angeles, CA)上的演讲后,发现除了这四大特性之外,还有一些鲜为人知的特性,甚至在微软官方的文档《C# Language Specification Version 2.0》中都没有提到。而这些特性却更加提高了语言的逻辑性。于是我编写了大量实际程序研究了这些特性,终于著成本文。本打算发表在《CSDN开发高手》杂志上的,可无奈水平有限,只能留在这个角落里贻笑大方了。希望能够对那些对C#语言有着浓厚兴趣的朋友有些帮助。

——lover_P 于北京工业大学1号楼221寝室

[修订说明]

  • 2004-08-24 下午
    第二次修订。感谢不知名的 fans1 朋友为我指出:原来using ns = namespace的语法在C#1.x中就支持。现在已经删除了。希望自己以后能够小心些…… '-_-
  • 2004-08-24
    第一次修订。修改了大量的错别字和文法错误。添加了对global限定符的介绍。

[正文]

微软在其即将推出的C#2.0(Visual C# Whidbey)中,添加了许多令程序员感到振奋的新特性。除了泛型(Generic)、迭代器(Iterator)、匿名方法(Anonmynous)和不完整类型(Partial Type)等重大的改进,还对现有的语法细节进行了很大的改进,极大地方便了.NET框架程序设计的工作,并且进一步加强了C#语言独有的高逻辑性。在本文中,我将向大家介绍一下这些改进。(文中C#指代的是C#1.2及以前的版本,而C#2.0指代的是微软尚未正式推出的C# Whidbey;文章中的所有代码均在版本号为8.00.30703.4的C#编译器下进行了测试,标有*的错误消息得自版本号为7.10.3052.4的C#编译器。)

[内容]

 

静态类

使用C#进行.NET框架程序设计的人应该都知道,无法将一个类声明为静态的。例如,下面的类声明:

public static class A {
static int i;
}

在C#中是无效的,当我们尝试编译这段代码时会得到下面的编译错误*:

error CS0106: 修饰符“static”对该项无效

由于无法用static修饰符修饰一个类,我们在类中总是能够既声明静态成员又声明实例成员。这无疑会带来很大的灵活性。但是,如果我们希望一个类是静态的,也就是希望强制要求这个类中的所有成员都应该为静态的,就无能为力了,唯一能做的就是自己注意将所有的成员声明为static。当我们忘记对一个本应是静态的成员使用static修饰符(尽管这是一个“低级错误”,但仍有可能发生)时,将会产生难以预料的错误。最重要的是,对于一个逻辑上的静态类(所有成员均使用static修饰符进行声明的类),我们甚至可以声明该类的一个变量并使用new操作符产生该类的实例!这显然不是我们所期望的。

而在C#2.0中,则提供了静态类这一概念,允许static修饰符对类进行修饰,上面的代码得以通过编译。如果一个类声明中包含了static修饰符,那么这个类中的所有成员将被强制要求声明为静态的。这时,如果我们故意在类中声明实例成员或是不小心忘记了成员声明中的static修饰符,如下面代码所示:

public static class A {
int i;
}

则编译器会报告错误:

error CS0708: 'A.i': cannot declare instance members in a static class

同时,如果我们声明该类的变量或是试图建立该类的一个实例时,如下面的代码:

public class Test {
A a; // error CS0723
void Foo() {
a = new A(); // error CS0712
}
}

则会得到下面的两个编译错误:

error CS0723: Cannot declare variable of static type 'A'
error CS0712: Cannot create an instance of the static class 'A'

很显然,C#2.0中对静态类的支持极大程度地避免了我们在书写程序中的意外失误,尤其是加强了静态类的逻辑性,提高了代码的可读性。

属性的可访问性限定

C#为我们提供了相当方便的属性定义,使得我们可以像访问类的公有变量成员那样访问类的属性,但还可以同时得到像访问函数那样的安全性。然而,C#只允许属性的设置动作(set{...})和获取动作(get{...})具有相同的可访问性(由属性声明中的publicinternalprivate等修饰符指定)。那么,当我们希望允许从任何程序集中的类获取一个特定类的属性,但只允许该类所在的程序集或该类的私有成员才能设置该属性时,我们只能将这个属性声明为公有且只读(即使用public修饰符声明但只有get{}域),而内部的或私有的成员只能通过设置与该属性相关的内部或私有的变量成员的值来完成属性的设置工作:

public class A {
int _intValue; // 与属性相关的一个int类型的成员变量

// 公有且只读的属性,允许任何类获取该属性的值:
public int Value {
get { return _intValue; }
}

// 下面的方法需要设置上面的属性,
// 但只能通过访问私有成员变量来完成,
// 并且要另外进行错误处理
private void SomeMethod() {
int i;
// ......
// 下面的if-else语句仅用来设置属性值:
if(0 < i && i < 10) {
_intValue = i;
}
else {
// 错误处理
}
}
}

很明显,这种做法是非常麻烦的。如果在多个地方改变了成员变量的值会使代码变得冗长不可读,还很有可能会产生错误,譬如该类有另外一个方法:

private void AnotherMethod() {
int i;
// ......
// 下面的if-else语句仅用于设置属性值,
// 但其对i的区间检测发生了错误
if(0 < i && i <= 10) { // 注意这里的 <= 运算符
_intValue = i;
}
// 并且没有进行错误处理
// ......
}

上面的方法对将要赋给私有变量成员的值的检查区间是错误的,这种错误是很有可能发生的。一旦调用了这个方法,_intValue很有可能具有错误的值,而访问了Value属性的外部程序集将会出现逻辑错误。这种错误的解决是相当困难的。并且,如果一个小组中的其他成员负责设计同一程序集中其他的类,要求他们在方法中书写如此大量的代码并要进行错误检查是不人道的。

当然,我们可能会想到将这种设置属性值的工作放到一个内部方法中集中进行:

// 程序集内部的类或该类的私有成员通过
// 下面的内部方法对上面的属性进行设置工作
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允许我们对属性的get{}set{}域分别设置可访问性,我们能够将上面的代码简单地写作:

public class A {
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#程序的可读性和语言逻辑性,写出的程序也具有更强的可维护性。

global限定符

在C#2.0以前,在使用命名空间时还有一个非常细微的问题。这就是C#命名空间的查找方式。考虑下面这个例子:

using System;

namespace MyNamespace {
namespace System {
public class MyConsole {
public void WriteLine(String str) {
System.Console.WriteLine(str); // 注意这一行!
}
}
}
}

这里我在自己的命名空间内声明了一个System命名空间,并在其中模拟了控制台类。我希望它通过调用System.Console类的方法来模拟控制台的行为。这个程序片断是没有语法问题的,当仍然不能通过编译。其主要原因是C#的命名空间作用域和普通变量的作用域规则类似,总是查找最近的声明,并且内部声明可以覆盖外部声明。因此,这段代码中标有注释的一行在编译的时候编译器会提示找不到类MyNamespace.System.Console——它试图在我自己的命名空间里找System.Console类!

在C#2.0以前,这个问题对于类库的设计者来说是非常头疼的。唯一的解决方法就是尽量在自己的命名空间内不使用全局命名空间中的名字。但是,由于类库开发者众多,难免会出现类似的情况;而且,这样做还会导致既是在自己的命名空间中也不能使用可读性高而又简洁的名字,这无疑伤害了语言的逻辑性和简洁性。

然而,C#2.0引入了global关键字,允许我们从全局选取命名空间。下面这个图示从.net命名空间的布局说明了global关键字的地位:


图示1:global关键字的地位
在C#1.x中:

+++ Who's the root? +++
|
+- System (namespace)
| |
| +- Console (class)
| +WriteLine (method)
| +- Int32 (struct)
| +- ...
|
+- MyNameSpace (namespace)
|
+- System ([sub]namespace)
+ MyConsole (class)

在C#2.0中

global (!!!ROOT!!!)
|
+- System (namespace)
| |
| +- Console (class)
| +WriteLine (method)
| +- Int32 (struct)
| +- ...
|
+- MyNameSpace (namespace)
|
+- System ([sub]namespace)
+ MyConsole (class)


这样一来,我们就能通过使用global关键字轻易地解决命名空间的冲突问题。上面的例子也就能够重写为:

using System;

namespace MyNamespace {
namespace System {
public class MyConsole {
public void WriteLine(String str) {
global.System.Console.WriteLine(str); // 注意这一行!
}
}
}
}

编译器指令

在我们调试C#程序时,经常会声明一些临时变量用来监测程序状态,并在调试完成后将这些声明删除。而当我们声明了这样的临时变量,在调试过程中却没有用到的时候,我们通常会得到大量的如:

warning CS0168: The variable 'exp' is declared but never used

的警告。然而,我们很清楚这样的警告是无害的。同样,很多其他时候我们也会得到一些警告,但我们不得不从大量的无害的警告中寻找我们需要的错误消息。

然而,C#2.0为我们提供了一条新的编译器指令:pragma warning,使得我们能够在一段代码中禁止一些我们确认无害的警告(通过指定警告的编号)。以前,这种工作只能由特定的编译器选项(譬如Microsoft Visual C#编译器的/nowarn)或相应的IDE选项(如Microsoft Visual Studio .NET 2003中的项目属性页中的相应选项)来完成。而且,通过编译环境来隐藏警告将导致在编译整个项目或整个源文件的过程中所有相应的警告都会被隐藏。如果我们仅仅知道在某一个代码块中一个警告是无害的,但对于代码的其它部分,我们还是希望看到这个警告消息时,这种做法就无能为力了。这个时候,我们只有通过pragma warning指令来命令编译器仅仅隐藏某一个代码块中相应的警告。我们可以用下列代码来禁止产生上面的例子中所述的“未使用参数”的警告:

public class Test {
public void SomeMethod() {
// 下面的编译器指令禁止了“未使用参数”的警告:
#pragma warning disable 0168
int tempStatus;
// ......
// 下面的编译器指令重新允许产生“未使用参数”的警告:
#pragma warning restore 0168
}
}

这样,当编译器编译SomeMethod()方法时,将不会产生上述的“未使用参数”的警告,但在编译其它代码段时,仍然会产生该警告,因为我们用#pragma warning restore指令重新打开了该警告。

固定大小缓冲区

最后,除了上述的一些特性外,C#2.0还提供了“固定大小缓冲区(Fixed Size Buffers)”的新特性。即像C语言那样可以在结构中声明一个固定大小的数组,这通过System.Runtime.CompilerServices.FixedBufferAttribute属性和Fixed关键字实现(参见参考文献第26页):

[System.Runtime.CompilerServices.FexedBuffer]
public struct Buffer {
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文档。

[回顶端]

分享到:
评论

相关推荐

    C#2.0标准文档(官方语言规范)

    C# 2.0在C# 1.0的基础上进行了多方面的增强和改进,引入了许多新特性,为程序员提供了更强大的工具集。 1. 泛型:C# 2.0引入了泛型,这是一个革命性的变化。泛型允许开发者定义可以处理多种数据类型的类、接口和...

    完全手册:c#2.0程序设计详解电子教程

    通过《完全手册:C#2.0程序设计详解电子教程》,开发者不仅能学习到C# 2.0的基本语法,还能深入理解面向对象编程的原则,掌握各种高级特性,并学会如何在实际项目中灵活运用。这份教程将引导你成为一位熟练的C# 2.0...

    C# 2.0 宝典的源代码

    通过阅读和分析这些代码,你将加深对C# 2.0语法、面向对象编程概念以及.NET Framework的理解,为后续学习C# 3.0、4.0乃至最新版本打下坚实基础。 总之,《C# 2.0 宝典》的源代码提供了宝贵的实践机会,无论是对初学...

    C#2.0完全参考手册源代码

    C# 2.0作为.NET Framework 2.0的一部分,引入了许多新的特性和改进,为开发者带来了更高的效率和灵活性。以下是其中的一些关键知识点: 1. **匿名方法**:在C# 2.0中,匿名方法允许我们定义没有名字的函数,可以...

    开发人员必看的C#2.0教程...

    10. **委托和事件**:C# 2.0改进了委托和事件的处理,使得事件处理更加简洁和安全,减少了内存泄漏的风险。 11. **Lambda表达式**:虽然C# 3.0才正式引入,但这个CHM文件可能也会涉及,lambda表达式是LINQ的基础,...

    C#2.0示例

    4. **属性改进**:C# 2.0允许在属性的访问器中使用`get`和`set`关键字,使得属性的实现更加简洁。此外,还支持了自动属性,简化了无特殊逻辑的属性定义。 5. **Generics(泛型)**:C# 2.0引入了泛型,允许创建类型...

    C#2.0新特性

    泛型是C# 2.0的一个重大改进,它允许创建类型参数化的类、接口和方法。这提供了更强的类型安全性和性能,因为类型检查可以在编译时完成,而不是在运行时。 8. **匿名对象**: 匿名对象允许在不定义具体类的情况下...

    C#2.0新的语法扩充(泛型,迭代器,匿名方法)

    ### C#2.0新语法扩充详解:泛型、迭代器与匿名方法 #### 泛型(Generics) 在C#2.0中引入的**泛型**是一种强大的功能,它允许开发人员创建类型安全的组件,这些组件可以用于多种数据类型而无需编写额外的代码。...

    C#2.0程序设计详解电子教程

    本书介绍了微软最新的编程语言C# 2.0,全书共分7篇共18章,从基础到应用,内容涉及C# 2.0语法、面向对象编程、使用Visual Studio 2005快速可视化编辑器、.NET类库的使用、使用ADO.NET 2.0进行数据库编程,以及使用C#...

    C#2.0锐利体验

    这部分可能涵盖了C#2.0中的一些其他重要改进,比如自动属性、属性的默认值、异常过滤器、增强的枚举支持等,并可能讨论C#语言未来的发展趋势,如C#3.0引入的LINQ、C#4.0的动态类型等。 通过学习这套课程,开发者将...

    C#2.0宝典源文件

    4. **属性和事件的改进**:C# 2.0对属性和事件进行了优化,支持自动实现的属性,使得声明简单属性变得更加便捷。同时,事件的实现也更加简洁,减少了冗余代码。 5. **Partial类**:在大型项目中,有时需要将类分布...

    C#2.0

    为了防止与现有标识符发生冲突,C#2.0没有添加任何新的关键字,而是赋予了`where`、`yield`和`partial`等词在特定上下文中的特殊含义。这种设计哲学确保了C#2.0的更新既强大又平滑,使得开发者能够在不破坏现有代码...

    用C#2.0实现网络蜘蛛

    标题中的“用C#2.0实现网络蜘蛛”是指使用C#编程语言的2.0版本来编写一个网络爬虫程序。网络爬虫是一种自动抓取网页内容的软件,它遍历互联网上的网页,收集信息并存储在本地,通常用于构建搜索引擎、数据分析或其他...

    C#2.0完全参考手册

    C#2.0完全参考手册.不错的工具书。相当于msdn。免费提供给大家

    C#2.0完全自学手册

    通过学习《C#2.0完全自学手册》,你不仅能掌握C# 2.0的基本语法和特性,还能深入了解如何在实际项目中运用这些知识,提升编程技能。书中可能还包含大量实例和练习,帮助读者巩固理论知识并提升实战能力。无论是初学...

    程序天下:C#2.0实例自学手册_200个例子.教程_光盘

    《程序天下:C#2.0实例自学手册_200个例子.教程_光盘》是一本专注于C# 2.0编程语言的实践学习资料。这本书通过200个精心设计的例子,旨在帮助读者从零基础开始,逐步掌握C# 2.0的核心概念和技术,最终达到能够独立...

    《C# 2.0实例自学手册 通过200个例子掌握Web开发捷径》一书配套光盘

    8. **AJAX支持**:C# 2.0和.NET Framework 2.0引入了对AJAX的支持,使Web应用可以实现部分页面刷新,提升用户体验。 9. **XML和Web服务**:C# 2.0提供了强大的XML处理能力,可以轻松创建和消费Web服务,这对于构建...

    C#2.0编写的电子钟

    这个项目不仅可以帮助初学者熟悉C# 2.0的基本语法,还能加深对.NET Framework的理解,同时锻炼到UI设计和事件处理的能力。通过实际动手编写这样一个简单但实用的应用,开发者能够更好地掌握C# 2.0的关键概念和技术。

    C#2.0 餐饮管理系统,C#2.0 餐饮管理系统,

    首先,C#2.0是.NET框架的一个重要版本,它在C#1.1的基础上进行了多方面的改进和增强。C#2.0引入了诸如匿名方法、迭代器、部分类和接口、自动属性、匿名类型等新特性,使得代码编写更加简洁和高效。在餐饮管理系统中...

    C#2.0新特性(CHM)

    本资源《C#2.0新特性(CHM)》是一部关于这一版本的详细指南,它涵盖了C# 2.0的关键更新,帮助开发者掌握这一阶段的编程技术。 一、匿名方法 匿名方法是C# 2.0引入的一个重要特性,允许在不定义单独函数的情况下直接...

Global site tag (gtag.js) - Google Analytics