C#中的部分方法(partial methods)

这是件非常有趣的事情,最近在工作中,使用了C#中的部分方法(partial methods)。其实,C#的部分方法并不是什么新Separation-of-Concerns-Feb-2013鲜事情,早在2007年左右,随着C# 3.0的发布,引入了部分方法的概念,它与部分类(partial class)的应用场景是类似的:解决代码生成器产生的代码与用户代码之间的隔离问题。 为什么需要隔离?原因很简单:由IDE设计器、T4代码模板转换、CodeDom等功能而带来的代码自动生成机制,会在不通知开发者的前提下,默默地将代码文件全覆盖重写,假如开发人员已经加入了一些自定义的代码,那么结果可想而知:这些自定义的代码会被冲掉。部分类(partial class)就是为了解决这样的问题,通过partial关键字,允许将类的方法和成员拆分到多个文件中,这样一来,自动生成的代码仅覆盖其中的一个文件,开发人员自定义的代码就放在另一个文件中,互相之间不影响。常见的部分类的使用场景有:Windows Forms中窗体、用户控件的代码,Entity Framework设计器自动产生的Context类等等。部分类的应用场景还是相对比较多的,开发人员也应该见得比较多,而相比之下,部分方法(partial methods)的使用量就比较少了。

关注点隔离

最近的项目中,需要在一个经典的Windows Forms应用程序中实现一个自定义的控件,这个自定义控件同时还需要能够支持在DataGridView控件的列中使用。从实现上看,这个自定义控件需要继承于UserControl类,并且实现IDataGridViewEditingControl接口:
public partial class MyUserControl : UserControl, IDataGridViewEditingControl
{
  // ...
}
然而,我并没有直接在上面的MyUserControl类定义上添加IDataGridViewEditingControl接口实现,而是新建了另一个代码文件,在这个代码文件中,使用部分类的特性,实现了IDataGridViewEditingControl接口,于是,从开发的角度隔离了自定义控件本身的业务逻辑,以及针对DataGridView所做的特殊定制。这样做的好处就是便于代码审核和维护:开发者能够非常快速方便地找到自定义控件本身的代码,或者是针对DataGridView的实现代码。这样的部分类的使用方式,与上述的代码自动生成的场景又有不同,它实现了开发人员的关注点隔离。 大致代码如下:
// MyUserControl.cs
public partial class MyUserControl : UserControl
{
  // 自定义控件本身的实现逻辑
}

// MyUserControl.DataGridViewSupport.cs
partial class MyUserControl : IDataGridViewEditingControl
{
  // 自定义控件对DataGridView的支持的代码
}

使用部分方法(partial methods)的理由

既然使用了部分类实现了关注点隔离,那么,我们同样有理由希望将不同业务相关的不同代码实现在不同的文件中。就该项目的具体情况而言,当控件所绑定的值发生变化的时候,应该调用DataGridView的NotifyCurrentCellDirty方法,来通知DataGridView,需要将当前单元格的值标记为“已更改”状态,进而触发值的保存。很明显,这部分代码是跟DataGridView相关的,需要放在单独的代码文件中;但是,又需要在自定义控件本身的实现逻辑中,当绑定的值发生变更的时候调用它,因此,调用代码是在自定义控件的实现逻辑中。于是,我使用了C#的部分方法特性:
// MyUserControl.cs
public partial class MyUserControl : UserControl
{
  // 自定义控件本身的实现逻辑
  private void ComboBoxSelectedIndexChanged()
  {
    this.NotifyValueChanged();
  }

  partial void NotifyValueChanged();
}

// MyUserControl.DataGridViewSupport.cs
partial class MyUserControl : IDataGridViewEditingControl
{
  // 自定义控件对DataGridView的支持的代码
  partial void NotifyValueChanged()
  {
    this.EditingControlDataGridView?.NotifyCurrentCellDirty(true);
  }
}
从实现上看,在MyUserControl.cs中,仅加入了一个部分方法(NotifyValueChanged方法)的声明代码,而其实现体是在另一个文件中。如果这个部分方法没有被实现,那么编译器在编译C#代码时,会删除这个方法声明,与此同时也会删掉所有调用部分的代码(也就是上面的this.NotifyValueChanged();这句代码也会被删掉)。从这个案例的情况看,即使我们没有实现NotifyValueChanged方法,也不会对整个自定义控件的实现带来什么大的影响。 再回顾一下在C#中使用partial methods的几条规则:
  • 各文件中同一个partial method的函数声明必须一致
  • partial methods不能有返回值
  • partial methods不能接受out参数
  • 不能在partial methods的声明上添加访问级别修饰符,partial methods都是private的

总结

不管是partial class还是partial method,主要目的有两个:
  1. 隔离用户代码和自动生成的代码
  2. 隔离开发人员的代码实现关注点
总结起来就是“隔离”两字。当然有时候也会给开发人员带来一定的困扰,比如一个类的所有代码没有放在一个文件中,会对代码定位造成一定的难度,甚至发现bug进行调试时,代码文件之间跳来跳去会让人眼花缭乱。因此,还是要推行“合理利用”的方针,比如:相关的代码文件都放在同一个文件夹下,防止partial method的滥用等等。C#是一门优雅的语言,只要使用合理,就能写出优雅的代码。
posted @ 2018-06-26 21:43  dax.net  阅读(2697)  评论(0编辑  收藏  举报