65.9K
CodeProject 正在变化。 阅读更多。
Home

用于创建C#访问者的工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (9投票s)

2008年12月14日

CPOL

4分钟阅读

viewsIcon

37924

downloadIcon

365

一个小的VS插件,用于实现访问者模式。

引言

在我上一篇文章中,我展示了如何为一个或多个类自动生成装饰器。在这篇文章中,我将展示一个更复杂的生成技术:通过创建一个访问者类并向现有类注入访问方法来自动生成访问者模式

要试用此插件,请下载包含二进制文件的Zip文件,并将其解压到您的Addins文件夹。

一些理论

如果我想将几种不同类型的对象翻译成C#,我通常会给它们一个类似以下的方法:

class MyEntity {
  // just an example!
  public something State { get; set; }

  // turn this object into c#
  public void ToCode(StringBuilder sb) 
  {
    sb.Append("static class " + State + " {}");
  }
  // other things here
}

上面的代码会获取被遍历(或访问)对象的State属性,并输出有效的C#代码。它看起来足够简单,您可以将这类语句散布在代码中。唯一的问题是,当您想输出另一种语言,例如Nemerle时。在这种情况下,您需要重构ToCode()方法,使其接受第二个参数——要生成的语言。这会导致以下结果:

public void ToCode(StringBuilder sb, Language language)
{
  switch (language) {
    case Language.CSharp:
      sb.Append("static class " + State + " {}");
      break;
    case Language.Nemerle:
      sb.Append("module " + State + " {}");
      break;
  }
}

在大量类中进行此更改是不现实的。在典型的层次结构中,您可能需要在数十甚至数百个位置更改此代码,这非常不方便。那么,您该怎么办?一种方法是,不使用StringBuilder,而是使用一个可继承的类(对StringBuilder的装饰器)。但是,之后,发出代码的语句不能在被访问类中完成,因为毕竟,被访问类不再知道(或关心)需要C#还是Nemerle代码。我的意思如下:

public void ToCode(CodeBuilder cb)
{
  cb.VisitMyEntity(this);
}

我们不做在被访问方法中做任何事情,而是进行控制反转,代码组装发生在构建器的`Visit`方法中。这样,C#构建器会以自己的方式追加该行,而Nemerle构建器则会以另一种方式追加。恭喜——您刚刚看到了一个功能齐全的访问者模式的例子。

一个示例

那么,问题是什么?问题是,层次结构有时非常庞大,并且手动编写所有这些代码(尤其是当您从未计划使类可以被访问时)是不现实的。对于一个包含20个类的层次结构,您需要创建一个具有20个VisitXXX方法的访问者,并在每个被访问类中添加20个方法。手动完成。这并非易事。

我的工具为您完成了这一切。操作方法如下。首先,右键单击项目并选择“添加”|“访问者”。

1.jpg

然后,为访问者命名并选择要访问的所有类。

2.jpg

现在,打开“方法”选项卡来自定义您的访问方法。在这里您可以定义:

  • 您的方法名称
  • 方法的任何附加参数
  • 在调用访问者自己的方法之前立即写入的代码
  • 在调用访问者自己的方法之后立即写入的代码

3.jpg

按“确定”后,会发生两件事。首先,您会得到一个生成的访问者类,类似以下内容:

class MySpecialVisitor
{
  /// <summary>
  /// Makes a visit to <see cref="MyEntity"/>.
  /// </summary>
  /// <param name="target">The element to visit.</param>
  public void VisitMyEntity(MyEntity target)
  {
    // todo: visit MyEntity
  }
  /// <summary>
  /// Makes a visit to <see cref="OtherEntity"/>.
  /// </summary>
  /// <param name="target">The element to visit.</param>
  public void VisitOtherEntity(OtherEntity target)
  {
    // todo: visit OtherEntity
  }
}

看到了吗?该类接受您选择的每种类型的对象作为参数?实际的处理代码由您来编写。现在,除此之外,您选择的每个类都添加了另一个方法。该方法看起来像这样:

/// <summary>
/// Allows this class to be visited by a <see cref="MySpecialVisitor"/>.
/// </summary>
/// <param name="visitor">The visitor.</param>
void SpecialVisit(MySpecialVisitor visitor, int somethingHere)
{
  // before the call
  visitor.VisitMyEntity(this);
  // after the call
}

瞧——该插件生成了访问者模式所需的所有必要连接。很简单,不是吗?

最终想法

本文继续了上一篇文章的主题,即代码生成。CodeBuilder装饰器被CSharpBuilder(在上一篇文章中讨论过)等构建器子类化,并且整个系统通过访问者模式在模型层次结构中传播。这使得它成为一个非常整洁易用的系统。

编写此插件的方式与上一篇类似:C#解析器会查看源代码并查找所有类。然后,我们根据用户的选择生成访问者类。添加方法有点棘手:由于没有适当的API可以向类声明中添加代码,我使用了一些老式的技巧来完成这项工作。我甚至在添加代码后重新格式化代码——这很难做到,因为如果您在后台保存文件在Visual Studio中进行格式化,您会得到一个非常奇怪的对话框,询问您更喜欢后台文件还是内存中的文件。幸运的是,API足够灵活,允许我们关闭打开的文件,向其写入数据,重新打开它们,进行格式化,然后关闭最初关闭的文件。所有这些都是为了方便。

这就是我要说的全部!如果您喜欢这篇文章,请投我一票!哦,您可能已经猜到了,另一篇与模式相关的文章即将到来。敬请期待!

© . All rights reserved.