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寝室

[正文]

    微软在其即将推出的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#程序的可读性和语言逻辑性,写出的程序也具有更强的可维护性。

命名空间别名

    在C#中,使用类(如声明成员变量或调用静态方法等)的时候需要指定类的完全名称,即命名空间前缀加类的名字。如果我们要在控制台上打印“Hello, world!”,则需要写:

System.Console.WriteLine(“Hello, world!”);

其中,System是命名空间,Console是类的名字,WriteLine()是我们要调用的方法。

    这样的要求显然会使代码变得异常冗余。因此,C#为我们提供了using关键字(指令)。通过使用using指令,我们可以向编译器指定一系列命名空间,当程序中出现了类名字时,编译器会自动到这些命名空间中查找这个类。因此,上面的代码可以写作:

using System;
// ......
public class Test {
    public static void Main() {
        Console.WriteLine(“Hello, world!”);
    }
}

    呵呵,这不就是经典的“Hello World”范例么?很显然,这种写法方便得多,可以极大地提高开发效率。

    然而,两个命名空间中很可能具有同名的类,而我们恰好需要用到这些同名的类。这种情况会经常发生。譬如在.net框架类库中就存在有三个Timer类:System.Timer.TimerSystem.Threading.TimerSystem.Windows.Forms.Timer。我们很可能需要两个Timer类:一个System.Timer.Timer用于在后台以固定的时间间隔检查应用程序或系统状态,一个System.Windows.Forms.Timer用于在用户界面中显示简单动画。这时,尽管我们使用了using指令,我们仍然需要在这些类出现时加上命名空间:

using System.Timer;
using System.Windows.Forms;
// ......
public class MainForm : Form {
    System.Timer.Timer CheckTimer;
    System.Windows.Forms.Timer AnimateTimer;
    // ......
}

    这样的程序仍然显得冗长。

    在C#2.0中,using指令的使用得到了扩展,我们可以使用using指令来为命名空间指定一个别名。当我们需要引用这个命名空间时,可以简单地使用它的别名。不过,使用别名和使用命名空间的使用完全相同。当我们引用一个命名空间中的类型的时候,只需要在命名空间后面加一个圆点“.”再跟上类型名称即可;而引用一个别名所代表的命名空间中的类型时,写法是一样的。那么,上面的例子可以写作:

using SysTimer = System.Timer;
using WinForm = System.Windows.Forms;
// ......
public class MainForm : WinForm.Form {
    SysTimer.Timer CheckTimer;  // 与命名空间的使用完全相同
    WinForm.Timer AnimateTimer;
    // ......
}

    我们可以看到,这样的代码要简洁得多。

编译器指令

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

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

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

    C#2.0为我们提供了一条新的编译器指令:pragma warning,使得我们能够在一段代码中禁止一些我们确认无害的警告(通过指定警告的编号)。以前,这种工作只能由特定的编译器(譬如C#编译器的/nowarn选项)或相应的IDE选项来完成。例如,我们可以用下列代码来禁止产生上述的“未使用参数”的警告:

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文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467500.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一个java类运行时从哪个方法开始_Java的应用程序是从类中的

【填空题】还不走,车来了。【填空题】进路是 运行时所经由的路径。【单选题】()是仿照法国巴黎圣母院设计建造的:【简答题】结合你学习的感受,你认为会计核算程序三大会计要素顺序可以颠倒吗?【填空题】一往无前马当先。【听力题】【判断题】企业所得税是直接税,其纳税人与负税…

C# 第一章 笔记

目录&#xff1a;硬盘 - 工具软件 - 编程开发 - Visual Studio软件&#xff1a;序列号、安装包、魔方虚拟光驱(win7) Visual Studio 2013.exeVisual Studio 2013.msiVisual Studio 2013.iso光盘 -> 光驱光盘镜像文件 -> 虚拟光驱 Win7安装会警告&#xff1a;您当前系统IE…

一粒老鼠屎

我又老生常谈了。本来很久都不去CSDN看文档的评论了。但是今天忍不住又瞄到了几眼&#xff0c;一看那些不屑的&#xff0c;骂人的腔调的评论&#xff0c;就心里堵得难受。CSDN的blog现在可想而知&#xff0c;当应很热闹。但透明和曾毅还有我都没有去用它的blog&#xff0c;我想…

我在深圳,但是家里托人在老家找了一份工作

最近&#xff0c;在微信公众号后台收到一份读者的留言&#xff0c;而且这位读者也是我们GX的&#xff0c;而且更幸运的是&#xff0c;跟我是一个地方的。她是疑惑如下~前辈你好~ 今天看到最新一篇有关读书的推文发现同是HC老乡&#xff0c;斗胆向您请教一些问题&#xff0c;有点…

Django之项目搭建和配置总结(一)

安装和创建虚拟环境 参考&#xff1a;linux系统下Python虚拟环境的安装和使用安装Django包 先进入虚拟环境&#xff0c;在联网下执行&#xff1a;pip install django1.8.7 1.8.7表示django的版本&#xff0c;如果不指定&#xff0c;会默认安装最新版的django。包会被安装到/usr…

java长连接例子_java实现长连接

();public Client(String serverIp, int port) {this.serverIpserverIp;this.portport;}public void start() throws UnknownHostException, IOException {if(running)return;socket new Socket(serverIp,port);System.out.println("本地端口&#xff1a;"socket.ge…

怎么把一个bool数组转成char?

这个是在知乎上看到的问题&#xff0c;分享给大家&#xff0c;如果有不同的答案&#xff0c;欢迎评论回复。知乎惯例谢邀人在厕所&#xff0c;刚刚蹲下因为最近都流行长文&#xff0c;但是作为日更而且更喜欢表达自己看法的我&#xff0c;力争文章是自己想发的&#xff0c;而且…

日本語勉強資源

ウェブ頁注音 http://www.hiragana.jp/ &#xff08;看新闻学背单词容易多了&#xff09;通訳 http://www.excite.co.jp &#xff08;单词&#xff0f;句子&#xff0f;网页翻译&#xff09;

mysql的高阶用法_MySQL的经典用法(十四)-高级优化

mysql的经典用法(十四)----高级优化基于/application/search/mysql/mysql-5.5.28/support-files/my-innodb-heavy-4G.cnf二次优化【删除--如果不使用主从同步】注释&#xff0c;去除无用日志文件# log-binmysql-bin# binlog_formatmixed【修改链接】# 最大链接值从100增加到512…

Kotlin——初级篇(二):变量、常量、注释

在Kotlin中的变量、常量以及注释多多少少和Java语言是有着不同之处的。不管是变量、常量的定义方式&#xff0c;还是注释的使用。下面详细的介绍Kotlin中的变量、常量、注释的使用。以及和Java的对比。 如果您还没有搭建环境和不了解Kotlin的&#xff0c;请看我的上一篇博文Kot…

java调用sql返回list_Spring JdbcTemplate实现有java.sql.ResultSet结果集返回的存储过程调用 | 学步园...

一、调用存储过程方式一&#xff1a;/*** 方法功能说明&#xff1a;通过存储过程分页&#xff0c;获取分页信息以及查询记录(装在list中)&#xff1b;* List.get(0) hsTable; //存放分页信息(tableName/token/pageNo/pageSize/recordsCount/pagesCount)--hsTable.get(key)* Lis…

今天研究了一下关于Asp.net文件上传时的内存消耗问题

晕啊&#xff0c;写了半天&#xff0c;突然间所有的格式都没了&#xff0c;我不得不在写字板中重新排版&#xff0c;DuDu哥&#xff0c;你还是快捷键都去掉吧。 简单研究了一下如何解决Asp.net连续上传文件时对内存占用越来越多的问题。但目前只能解决一部份问题&#xff0c;要…

Linux系统编程-管道入门

晚上好&#xff0c;继续记录我的学习心得。当你厌倦了自己的目标时&#xff0c;怎样继续保持专注&#xff1f;误区&#xff1a;成功人士说的都是自己如何“满怀热情”去努力实现他们的目标。不管是在商业、体育还是艺术界&#xff0c;我们听到的都是“一切都归结于激情”或者“…

JavaScript 基础,登录前端验证

1、<script></script>的三种用法&#xff1a; 1.放在<body>中 2.放在<head>中 3.放在外部JS文件中 2、三种输出数据的方式&#xff1a; 1.使用 document.write() 方法将内容写到 HTML 文档中。 2.使用 window.alert() 弹出警告框。 3.使用 innerHTML 写…

Meta的用法

----------------------------------------------- ? 这就是meta标签的典型运用。meta标签有多少参数&#xff0c;有什么作用&#xff0c;为什么我们要特别重视它呢&#xff1f;让我们一起来发掘meta标签的奥妙! ? 根据HTML语言标准注释&#xff1a;meta标签是对网站发展非常…

iOS10 打开APP设置界面和WIFI界面

在iOS10以上&#xff0c;权限这块有了一些变化 首先在info的URL Types 添加 prefs 1、打开APP设置界面 //打开设置let url:NSURL NSURL(string: UIApplicationOpenSettingsURLString)!if UIApplication.shared.canOpenURL(url as URL){ UIApplication.shared.openURL(url as…

一种简单、实用的测量程序运行时间的方法

前言 平时我们可能很少去关注程序运行的时间&#xff0c;但是在一些情况下可能需要对程序进行一个整体的复盘、优化。那么&#xff0c;程序运行的时间就是一个可以考虑的方面&#xff0c;可以测一下某些代码块、函数、算法的运行时间&#xff0c;然后整体考虑看看有没有必要进行…

可以这样给DataGrid加个序号列。

先给DataGrid加个绑定列&#xff0c;该列设置页眉文本&#xff0c;但“数据字段”留空&#xff0c;然后在ItemDataBound事件中加入下面代码。 privatevoiddgScore_ItemDataBound(objectsender, System.Web.UI.WebControls.DataGridItemEventArgs e) { …

工作几天被裁员,难受

前两天&#xff0c;一个读者朋友私聊我说&#xff0c;自己被公司裁员了&#xff0c;这是他的第一份工作&#xff0c;只工作了不到半个月就收到了裁员信息。他非常不甘心&#xff0c;也非常恐慌&#xff0c;不甘心的是&#xff0c;自己觉得工作已经非常努力&#xff0c;也有做出…

C++17 inline静态变量,静态变量不用再类内定义类外初始化了

而在 C17 引入的标准中&#xff0c;你可以直接在类中定义并初始化类静态成员变量&#xff0c;并且这个表达式还是constexpr&#xff0c;从而可以在编译时进行求值&#xff0c;提供更好的优化。 例如&#xff1a; // MyClass.h class MyClass { public:inline static int myVa…