C# 3.0中的分部方法

缘起兼序

  刚刚过去的这个周末对我来说非常之有意义。其一,周五搞来了一本《上帝掷骰子吗?》,一本讲量子理论的科普读物,昏天黑地地从周五晚上一直看到周六夜里,感触良多此处不便多表,总之在精神分裂的边缘转了一圈。周日早早起床迎接我的一些朋友,来了个私人的.NET技术交流会,交流了CodePlex应用、Linq和数据库的一些技术。

  而其中最让人兴奋的是,周六的时候Visual Studio 2008发布了Beta2版。有了Vista和VS2005的教训,我曾发誓不碰Beta2之前版本的任何工具和技术。但不代表我不关注这些技术。所以VS2008 Beta2出来之后,我第一时间用QQ的下载工具down了一个下来,但安装的时候竟然报告安装文件有错误,不得已只好到MSDN Subcribtion去用微软自己的工具重新下载,直到周日夜里,才终于下载完毕并安装好。

  安装完了之后,我也没力气玩了,直接睡觉。直到今天才终于能摸一摸。第一步,当然是去看What's New。

分部方法的语法

  在看C#语言的What's New时,突然发现新特性列表的最后,多出了一个“Partial Method Definitions ”,但并不像其他新特性一样有超链接链接到其说明。上网搜索了一下,关于分部类型的信息非常少。尤其是中文信息,只有CSDN的 周融 在其《C# 3.5 语言在 Visual Studio Orcas Beta 1 上的新增功能(二) 》一文的最后提到了这个分部方法,但没有进一步说明。英文技术文章中,倒是有两篇不错的:http://blogs.msdn.com/wesdyer/archive/2007/05/23/in-case-you-haven-t-heard.aspx 和 http://community.bartdesmet.net/blogs/bart/archive/2007/07/28/c-3-0-partial-methods-what-why-and-how.aspx

  又仔细看了一下MSDN Library for Visual Studio 2008 Beta 2,终于对这个语言特性有所了解,在这里介绍一下,希望对大家有所帮助。

  分部方法的定义和分部类型类似,只需在方法定义前添加partial关键字。但分部方法只能拆分成两个部分——一部分是定义声明(Definition Declaration),另一部分是实现声明(Implement Declaration)。其中定义声明看上去和抽象方法类似:

partial class CA
{
    // ...
    private void partial M();  // 定义声明| 

  而实现声明看上去和普通方法类似:

private void partial M()    // 实现声明
{
    // 方法体
}

  在调用分部方法时,和调用其他方法一样:

CA a = new CA();
a.M(); 

  只是,如果只有定义声明而没有编写实现声明,则编译器不会发射(Emit)该方法和调用该方法的语句的元数据与IL代码。换言之,如果没有编写实现声明,则编译得到的程序集中,CA类型里并没有M这个方法。

使用分部方法的注意事项

  分部方法的语法非常简单,但有一些事项要注意。 

  • 如果没有写实现声明,则不会发射方法调用代码,也不会对参数进行求值。因此,对于下面的例子:

class CA
{
    partial void M(int i);

    static void Main()
    {
        CA a = new CA();
        int i = 0;
        a.M(i++);
    }
}

  分部方法M只有定义声明,没有实现声明,因此也不会发射调用该方法的代码:a.M(i++),因此也不会对i++进行求值。所以最终i的值依然是0。但如果为M编写了实现声明,则a.M(i++)的代码会被编译到最终的程序集中,同时参数也被求值,i的值将被变为1。 

  • 分部方法只能出现在分部类中。
  • 分部方法必须是私有(private)的,并且返回值类型必须是void。
  • 分部方法可以带有参数,并且其参数可以带有this、params和ref修饰符,但不能带有out修饰符。
  • 分部方法不可以是虚拟(virtual)的。
  • 分部方法不可以是外部(extern)的。
  • 分部方法可以是静态(static)的,也可以是不安全(unsafe)的。
  • 分部方法可以是泛型方法,泛型约束必须放置在定义声明中,但也可以在事先声明中重复说明。在定义声明和实现声明中,类型参数和类型参数的名字不一定必须一致。
  • 不能将分部方法封装到一个委托中。

分部方法的应用场景

  分部方法和分部类型的初衷是类似的,一方面可以使得不同的开发者能够同时编写一个类型的不同部分,另一方面可以分离自动生成的代码和用户手写的代码。和分部类型一样,分部方法也会在编译初期被合并成一个方法定义。猜测:从微软的角度来看,第二个“初衷”可能才是真正的初衷。

  由此,分部方法有如下几个应用场景:(场景1 出自In Case You Haven't Heard这篇文章【http://blogs.msdn.com/wesdyer/archive/2007/05/23/in-case-you-haven-t-heard.aspx】),场景2 出自Visual Studio 2008的Linq to SQL技术,而场景3 则是Anders Liu自已臆想出来的。

场景1 轻量级事件处理

  有的时候,自动生成的代码需要事件这类语言构造来通知用户对某些操作进行处理,但实际上用于编写的代码就位于自动生成的类型之中。此时,或者需要触发一个事件,或者就需要生成一个virtual方法来让用户继承。但无论是事件还是继承,开销都是比较大的,所以可以通过分部方法来实现轻量级的处理方式。如下面的类:(本例子引用自前述的In Case You Haven't Heard一文)

partial class Customer
{
    string name; 

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            OnBeforeUpdateName();
            OnUpdateName();
            name = value;
            OnAfterUpdateName();
        }
    } 

    partial void OnBeforeUpdateName();
    partial void OnAfterUpdateName();
    partial void OnUpdateName();
}

  这里定义了三个分部方法,其意义不言而喻。假设这是系统自动生成的代码,则我们只需在另外一个源代码文件中的partial class Customer中实现这几个分部方法即可。 

场景2 自定义DataContext中的Insert、Update、Delete方法

  当使用Linq to SQL向项目中加入了实体类之后,还会创建一个XxxDataContext类,这个类继承自DataContext类,并且是partial的。这个类封装了具体的数据库操作功能(实体类仅封装数据库中的数据),如对象的插入、更新和删除等。

  下面我们来看一下这个自动生成的类定义:

[System.Data.Linq.Mapping.DatabaseAttribute(Name="AdventureWorks")]
public partial class AdventureWorksDataContext : System.Data.Linq.DataContext
{
  
    private static System.Data.Linq.Mapping.MappingSource mappingSource = new AttributeMappingSource();
  
    #region Extensibility Method Definitions
    partial void OnCreated();
    partial void InsertAWBuildVersion(AWBuildVersion instance);
    partial void UpdateAWBuildVersion(AWBuildVersion instance);
    partial void DeleteAWBuildVersion(AWBuildVersion instance);
...... 

  这里我们可以看到一系列的partial方法。其中第一个OnCreated实际上属于场景1中描述的情况,是一个轻量级的事件,表示DataContext环境对象创建完毕。而其他partial方法则用于自定义DataContext的IUD操作。对于每一个表(实体类),这里都会出现一组InsertXxx、UpdateXxx和DeleteXxx方法。如果我们希望自定义删除行为(如希望将一个IsDelete字段设置为true来表示已删除),则可以在另一个文件中扩展这个partial类,并为对应的Delete方法提供实现声明。

场景3 新的调试信息输出方法

  这是Anders Liu臆想的场景,在分部方法的协助下,我们可以写出这样的代码:

partial class CA
{
    partial void DebugPrint(string msg);
...
    void F()
    {
        ....
        DebugPrint("aaa");
    }

partial class CA
{
#if DEBUG
    partial void DebugPrint(string msg);
    {
        Debug.WriteLine(msg);
    }
#endif

  这样做的好处在于,我们还是反过来说罢,如果不这样做,必须在每次调用调试代码时都加入#if判断。而这样可以将调试代码都写成方法,在一处用#if进行判断。

  缺点在于,由于分部方法必须是私有的,所以必须针对每个类写一套调试代码。

小结 

  嗯,总而言之,Anders Liu在这篇文章里说的是分部方法。

posted @ 2007-07-30 21:49 Anders Liu 阅读(...) 评论(...) 编辑 收藏