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

XML 编译器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (24投票s)

2005年9月13日

CPOL

9分钟阅读

viewsIcon

98947

downloadIcon

1288

使用 CodeDom 将您的 XML 对象图转换为代码。

引言

在过去一年半的时间里,我将 MyXaml 开发成我认为相当成熟的通用XML实例化引擎,我编写了一个“lite”版本,名为 MycroXaml,开发了一个 简单的实用工具,它可以获取XML并生成适合声明性解析器实例化的底层命令式类和属性,并且 探索 了声明性编程的优缺点。许多人要求,但这个套件中缺少的一个工具是能够获取声明性代码并生成命令性代码。为了解决这个需求,本文介绍了XML编译器的初步版本。我在这里展示的工作是获取 MyXaml 风格的XML,并使用CodeDom生成实例化XML对象图的命令式代码。

如果您不熟悉声明性编程概念,我建议您阅读我的文章“比较声明性代码和命令式代码”作为两种编程风格差异的介绍。

现在,让大家感到困惑。虽然 MyXamlMycroXaml 可能有一些品牌知名度,但我将慢慢地放弃“xaml”这个术语。您将开始看到文件和XML节点使用“declara”这个术语。我决定这样做是因为我编写并将继续编写的通用实例化引擎和支持工具正在朝着与Microsoft的XAML非常不同的方向发展,坦率地说,我不希望我的工具与Microsoft的工具混淆。就这样。这就是我的“公关”手法!

Mono怎么样?

Iain McCoy一直在将XAML编译器作为Google“编程之夏”项目的一部分进行实现。您可以在 这里 阅读一些相关内容,或者直接Google搜索“XAML compiler”。我所做的和Iain似乎正在努力的方向之间有几个显著的差异。

  • 我无意遵循Microsoft的XAML命名空间语法。
  • 我无意遵循隐式集合语法(注意 IAddChild 接口)。我所有的解析器都严格遵循“类-属性-类”的层次结构。
  • 我希望生成的代码是“人类可读的”。在某种程度上,如果两个不同的命名空间具有相同的类名,这会给我带来一些麻烦。这将在我重构代码后解决。
  • 除了人类可读性,我还希望将 string 到属性的转换看起来自然且尽可能高效。因此,我没有使用 w.SecondsToDestruction = (new Int32Converter()).ConvertFromString("5"); 以及用于 PointSizeFont 转换器的类似构造,而是实现了一种通用机制来发出适当的 CodeDom,例如,生成的代码将如下所示:m.SecondsToDestruction=5;。编译代码的目的是消除所有类型转换,对吗?另一方面,这意味着我目前不支持某些 struct 和其他类,但它们肯定很容易添加。

所以,这基本上总结了Iain所做和我所做之间的差异。如您所见,我将自己与XAML分离的理由是合理的。

话不多说...

XML 编译器

XML 编译器使用了 MycroXaml 文章中最初描述的代码,但它已经有所演变。我选择这个代码库是因为我希望为编译器的第一遍提供一个更简单的原型开发环境。原始代码库已经过修改,以便在解析过程中触发各种事件。

public event InstantiateClassDlgt InstantiateClass;
public event AssignPropertyDlgt AssignProperty;
public event AssignEventDlgt AssignEvent;
public event SupportInitializeDlgt BeginInitCheck;
public event SupportInitializeDlgt EndInitCheck;
public event EventHandler EndChildProcessing;
public event AddToCollectionDlgt AddToCollection;
public event UseReferenceDlgt UseReference;
public event AssignReferenceDlgt AssignReference;
public event CommentDlgt Comment;

编译器会钩住这些事件并实例化各种 CodeDom 语句。您仍然可以使用解析器在运行时实例化对象图,或者您可以使用 CodeGen 工具生成 C#、VB 或其他 CodeDom 可发出的代码并进行编译和组装。解析器的另一个显著变化是,当它处于“代码生成”模式时,没有任何东西被实例化,也没有任何属性被分配,因此代码必须更健壮以处理这种情况。

单元测试

首先,我需要编写各种单元测试来验证解析器和代码生成器的基本功能。

这些是伪单元测试,因为它们实际上不测试生成的源代码,它们只测试解析器和代码生成器在编译 XML 过程中没有发现错误。因此,例如,“SimpleClass”单元测试

[Test]
public void SimpleClass()
{
  string xml="<?xml version='1.0' encoding='utf-8'?>\r\n";
  xml+="<!-- (c) 2005 MyXaml All Rights Reserved -->\r\n";
  xml+="<Declara Name='CodeGenTest'\r\n";
  xml+=" xmlns:def='Definition'\r\n";
  xml+=" xmlns:ref='Reference'\r\n";
  xml+=" xmlns:wf='System.Windows.Forms, System.Windows.Forms,
         Version=1.0.5000.0, Culture=neutral, 
         PublicKeyToken=b77a5c561934e089'>\r\n";
  xml+=" <wf:Form Name='appMainForm'/>";
  xml+="</Declara>\r\n";

  XmlDocument doc=new XmlDocument();
  doc.LoadXml(xml);
  CodeGen cg=new CodeGen(doc, "CodeGenTest", 
         "Clifton.CodeGenTest", "CodeGenTest");
}

调用代码生成器并发出以下代码

namespace Clifton.CodeGenTest 
{ 
  using System.Windows.Forms; 

  public class CodeGenTest 
  { 
    private Form appMainForm; 

    public CodeGenTest() 
    { 
    } 

    public virtual object Initialize() 
    { 
      appMainForm = new Form(); 
      appMainForm.SuspendLayout(); 

      appMainForm.Name = "appMainForm"; 
      appMainForm.ResumeLayout(); 

      return this.appMainForm; 
    } 
  } 
}

最终测试用例

我使用的最后一个测试用例是颜色选择器的示例

这个小程序涉及到几个控件、数据绑定和事件处理程序的连接。完整的标记如下所示

<?xml version="1.0" encoding="utf-8"?>
<Declara Name="Form"
  xmlns:wf="System.Windows.Forms, System.Windows.Forms,
                    Version=1.0.5000.0, Culture=neutral, 
                    PublicKeyToken=b77a5c561934e089"
  xmlns:ctd="Clifton.Tools.Data, Clifton.Tools.Data"
  xmlns:ev="Events, Events"
  xmlns:def="Definitions"
  xmlns:ref="References">
  <wf:Form Name="appMainForm"
             Text="Color Chooser"
             ClientSize="400, 190"
             BackColor="White"
             FormBorderStyle="FixedSingle"
             StartPosition="CenterScreen">

    <ev:TrackbarEvents def:Name="events"/>

    <wf:Controls>
      <!-- Instantiate Trackbars -->
      <wf:TrackBar Name="redScroll" Orientation="Vertical"
        TickFrequency="16" TickStyle="BottomRight" Minimum="0"
        Maximum="255" Value="128" 
        Scroll="{events.OnScrolled}" Size="42, 128"
        Location="10, 30" Tag="R"/>
      <wf:TrackBar Name="greenScroll" Orientation="Vertical"
        TickFrequency="16" TickStyle="BottomRight" Minimum="0"
        Maximum="255" Value="128" 
        Scroll="{events.OnScrolled}" Size="42, 128"
        Location="55, 30" Tag="G"/>
      <wf:TrackBar Name="blueScroll" Orientation="Vertical"
        TickFrequency="16" TickStyle="BottomRight" Minimum="0"
        Maximum="255" Value="128" 
        Scroll="{events.OnScrolled}" Size="42, 128"
        Location="100, 30" Tag="B"/>

      <!-- Instantiate Labels -->
      <wf:Label Size="40,15" TextAlign="TopCenter"
        Font="Microsoft Sans Serif, 8.25pt, style= Bold"
        Location="10, 10" ForeColor="Red" Text="Red"/>
      <wf:Label Size="40,15" TextAlign="TopCenter"
        Font="Microsoft Sans Serif, 8.25pt, style= Bold"
        Location="55, 10" ForeColor="Green" Text="Green"/>
      <wf:Label Size="40,15" TextAlign="TopCenter"
        Font="Microsoft Sans Serif, 8.25pt, style= Bold"
        Location="100, 10" ForeColor="Blue" Text="Blue"/>

      <wf:Label Name="redValue" Size="40,15" TextAlign="TopCenter"
        Font="Microsoft Sans Serif, 8.25pt, style= Bold"
        Location="10, 160" ForeColor="Red" Text="128">
      </wf:Label>
      <wf:Label Name="greenValue" Size="40,15" TextAlign="TopCenter"
        Font="Microsoft Sans Serif, 8.25pt, style= Bold"
        Location="55, 160" ForeColor="Green" Text="128">
      </wf:Label>
      <wf:Label Name="blueValue" Size="40,15" TextAlign="TopCenter"
        Font="Microsoft Sans Serif, 8.25pt, style= Bold"
        Location="100, 160" ForeColor="Blue" Text="128">
      </wf:Label>

      <!-- Instantiate PictureBox -->
      <wf:PictureBox Name="colorPanel" Location="90, 0" Size="200, 100"
        Dock="Right" BorderStyle="Fixed3D" BackColor="128, 128, 128"/>

    </wf:Controls>

    <!-- Set PictureBox instance in event handler. -->
    <ev:TrackbarEvents ref:Name="events" 
                           ColorPanel="{colorPanel}"/>

    <!-- Wire up TrackBar.Value to Label.Text -->
    <ctd:BindHelper Source="{redScroll}" SourceProperty="Value"
      Destination="{redValue}" DestinationProperty="Text" 
      ImmediateMode="true"/>
    <ctd:BindHelper Source="{greenScroll}" SourceProperty="Value"
      Destination="{greenValue}" DestinationProperty="Text"
      ImmediateMode="true"/>
    <ctd:BindHelper Source="{blueScroll}" SourceProperty="Value"
      Destination="{blueValue}" DestinationProperty="Text"
      ImmediateMode="true"/>
  </wf:Form>
</Declara>

数据绑定

关于声明式编程,我发现的一件事是我更喜欢将 UI 对象图与数据绑定(实际上,甚至事件连接)分开。我这里使用的数据绑定不是 .NET 的数据绑定;相反,我使用的是我的文章 理解简单数据绑定 中描述的代码。虽然在技术上不是必需的,但它更方便,因为 .NET 没有 Binding 类的默认构造函数,这使得它不适合在没有包装器的情况下进行声明式实例化。

事件处理程序

事件处理程序放在它自己的程序集中。本质上,这就是您如何为UI和其他事件编写特定于应用程序的实现——一个程序集(或多个程序集)包含命令式代码,而声明式代码映射命名空间并实例化类。在上面的XML中,您会注意到TrackbarEvents类在过程的早期被实例化,以便可以将TrackBar.Scroll事件连接到OnScrolled事件处理程序。稍后,TrackbarEvents实例被*引用*,以便可以将PictureBox实例分配给它。所有这些的原因是MycroParser不处理前向引用。事件处理程序本身很简单,将R、G和B通道组合成一个Color结构并将其分配为背景颜色。

using System;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;

namespace Events
{
  public class TrackbarEvents
  {
    protected byte[] channels=new byte[3] {128, 128, 128};
    protected Hashtable channelMap;
    protected PictureBox colorPanel;

    public PictureBox ColorPanel
    {
      get {return colorPanel;}
      set {colorPanel=value;}
    }

    public TrackbarEvents()
    {
      channelMap=new Hashtable();
      channelMap["R"]=0;
      channelMap["G"]=1;
      channelMap["B"]=2;
    }

    public void OnScrolled(object sender, EventArgs e)
    {
      TrackBar track=(TrackBar)sender;
      int idx=(int)channelMap[track.Tag];
      channels[idx]=(byte)track.Value;
      colorPanel.BackColor = Color.FromArgb(channels[0],
                               channels[1], channels[2]);
    }
  }
}

生成的“编译”C# 代码如下所示(我已剪掉冗余部分)

namespace Clifton.CodeGenTest
{
  using System.Windows.Forms;
  using Clifton.Tools.Data;
  using Events;
  using System.ComponentModel;
  using System.Drawing;

  public class CodeGenTest
  {
    private Form appMainForm;
    private TrackbarEvents events;
    private TrackBar redScroll;
    private TrackBar greenScroll;
    private TrackBar blueScroll;
    private Label label1;
    private Label label2;
    private Label label3;
    private Label redValue;
    private Label greenValue;
    private Label blueValue;
    private PictureBox colorPanel;
    private BindHelper bindHelper1;
    private BindHelper bindHelper2;
    private BindHelper bindHelper3;

    public CodeGenTest()
    {
    }

    public virtual object Initialize()
    {
      appMainForm = new Form();
      appMainForm.SuspendLayout();

      events = new TrackbarEvents();

      // Instantiate Trackbars 

      redScroll = new TrackBar();
      redScroll.BeginInit();
      redScroll.SuspendLayout();

      redScroll.Name = "redScroll";
      redScroll.Orientation = Orientation.Vertical;
      redScroll.TickFrequency = 16;
      redScroll.TickStyle = TickStyle.BottomRight;
      redScroll.Minimum = 0;
      redScroll.Maximum = 255;
      redScroll.Value = 128;
      redScroll.Scroll += 
         new System.EventHandler(events.OnScrolled);
      redScroll.Size = new Size(42, 128);
      redScroll.Location = new Point(10, 30);
      redScroll.Tag = "R";
      redScroll.EndInit();
      redScroll.ResumeLayout();
      appMainForm.Controls.Add(redScroll);

      ... snipped green and blue ...

      // Instantiate Labels 

      label1 = new Label();
      label1.SuspendLayout();

      label1.Size = new Size(40, 15);
      label1.TextAlign = ContentAlignment.TopCenter;
      label1.Font = new Font("Microsoft Sans Serif", 
                                   8, FontStyle.Bold);
      label1.Location = new Point(10, 10);
      label1.ForeColor = Color.Red;
      label1.Text = "Red";
      label1.ResumeLayout();
      appMainForm.Controls.Add(label1);

      ... snipped the other two labels ...

      redValue = new Label();
      redValue.SuspendLayout();

      redValue.Name = "redValue";
      redValue.Size = new Size(40, 15);
      redValue.TextAlign = ContentAlignment.TopCenter;
      redValue.Font = new Font("Microsoft Sans Serif", 
                                    8, FontStyle.Bold);
      redValue.Location = new Point(10, 160);
      redValue.ForeColor = Color.Red;
      redValue.Text = "128";
      redValue.ResumeLayout();
      appMainForm.Controls.Add(redValue);

      ... snipped the green and blue value labels ...

      // Instantiate PictureBox 

      colorPanel = new PictureBox();
      colorPanel.SuspendLayout();

      colorPanel.Name = "colorPanel";
      colorPanel.Location = new Point(90, 0);
      colorPanel.Size = new Size(200, 100);
      colorPanel.Dock = DockStyle.Right;
      colorPanel.BorderStyle = BorderStyle.Fixed3D;
      colorPanel.BackColor = Color.FromArgb(128, 128, 128);
      colorPanel.ResumeLayout();
      appMainForm.Controls.Add(colorPanel);

      // Set PictureBox instance in event handler. 

      events.ColorPanel = colorPanel;

      // Wire up TrackBar.Value to Label.Text 

      bindHelper1 = new BindHelper();
      bindHelper1.BeginInit();

      bindHelper1.Source = redScroll;
      bindHelper1.SourceProperty = "Value";
      bindHelper1.Destination = redValue;
      bindHelper1.DestinationProperty = "Text";
      bindHelper1.ImmediateMode = true;
      bindHelper1.EndInit();

      ... snipped the other two binder setups ...

      appMainForm.Name = "appMainForm";
      appMainForm.Text = "Color Chooser";
      appMainForm.ClientSize = new Size(400, 190);
      appMainForm.BackColor = Color.White;
      appMainForm.FormBorderStyle = 
                      FormBorderStyle.FixedSingle;
      appMainForm.StartPosition = 
                   FormStartPosition.CenterScreen;
      appMainForm.ResumeLayout();

      return this.appMainForm;
    }
  }
}

一个很棒的功能是,您的XML代码中的注释会作为注释放置在编译后的源代码中!

执行

CompileAndRun”单元测试创建程序集,实例化类,调用Initialize方法,然后对生成的Form实例执行ShowDialog

[Test]
public void CompileAndRun()
{
  XmlDocument doc=new XmlDocument();
  doc.Load(
    "..\\..\\Demos\\MycroParser\\bin\\debug\\ColorPicker.declara");
  CodeGen cg=new CodeGen(doc, "Form", 
                 "Clifton.CodeGenTest", "CodeGenTest");
  string source=cg.Source;

  RunTimeCompiler rtc=new RunTimeCompiler();
  Assembly assembly=rtc.Compile(cg.Assemblies, "C#", 
                    source, String.Empty, 
                    Assembly.GetExecutingAssembly().Location);
  object refObj=Activator.CreateInstance(
                 assembly.GetModules(false)[0].GetTypes()[0]);
  object ret=refObj.GetType().InvokeMember("Initialize", 
               BindingFlags.Public | BindingFlags.Instance | 
               BindingFlags.InvokeMethod, null, refObj, null);

  // Avoid including System.Windows.Forms in this namespace.
  // It's bad enough we're already including 
  // Clifton.Tools.Compiler and Events!
  ret.GetType().InvokeMember("ShowDialog", BindingFlags.Public | 
               BindingFlags.Instance | BindingFlags.InvokeMethod, 
               null, ret, null);
}

我不会详细介绍 RunTimeCompiler 类,因为那与主题无关。

CodeDom

使用 CodeDom 很有趣,所以我将展示各种解析器事件处理程序中的代码片段。

实例化包装器类

包装器类包含编译代码的字段、默认类构造函数和 Initialize 方法。为了构建这个 CodeDom 单元,必须初始化 CodeDom

provider=new CSharpCodeProvider();
gen=provider.CreateGenerator();

接着创建代码编译单元并添加命名空间单元

CodeCompileUnit ccu=new CodeCompileUnit();
cns=new CodeNamespace(ns);
ccu.Namespaces.Add(cns);

然后添加类类型声明、代码构造函数和 Initialize 方法

ctd=new CodeTypeDeclaration(className);
cns.Types.Add(ctd);

constructor=new CodeConstructor();
constructor.Attributes=MemberAttributes.Public;
ctd.Members.Add(constructor);

method=new CodeMemberMethod();
method.Name="Initialize";
method.ReturnType=new CodeTypeReference("System.Object");
method.Attributes=MemberAttributes.Public;
ctd.Members.Add(method);

实例化一个类

当遇到XML节点时,它要么是类实例化,要么是类属性。如果是类实例化,生成器必须创建该类型的字段。

CodeMemberField cmf=
    new CodeMemberField(cea.Type.Name, name);
cmf.Attributes=MemberAttributes.Private;
ctd.Members.Add(cmf);

然后添加适当的“new”代码赋值

CodeAssignStatement instantiator=
               new CodeAssignStatement(
                  new CodeVariableReferenceExpression(name),
                  new CodeObjectCreateExpression(cea.Type.Name, 
                                        new CodeExpression[] {}));

method.Statements.Add(instantiator);

请注意,没有参数传递给构造函数。

分配属性值

分配属性值是许多魔法发生的地方。

分配枚举值

enum 赋值有点复杂,因为有些属性可以接受一组通过二进制 OR 组合的 enum,例如 Anchor 属性。以下代码演示了如何生成 CodeDom 来处理这种情况。以下代码并非 100% 健壮,但它是一个很好的开始

protected CodeExpression EvalEnums(string itemStr, 
                                     string typeName)
{
  string[] items=itemStr.Split(',');
  string item=items[0];

  CodeBinaryOperatorExpression expr2=null;

  // Get the left operand.
  CodeExpression expr=new CodeFieldReferenceExpression(
                 new CodeTypeReferenceExpression(typeName),
                 item.Trim());

  // If multiple styles, the "root" 
  // expression is a binary operator 
  // instead of the field reference.
  if (items.Length > 1)
  {
    expr2=new CodeBinaryOperatorExpression();
    expr2.Operator=CodeBinaryOperatorType.BitwiseOr;

    // Add the first field reference as the left side 
    // of the binary operator.
    expr2.Left=expr;

    // Make the binary operator the "root" expression.
    expr=expr2;
  }

  // If the string consists of multiple styles...
  for (int i=1; i<items.Length; i++)
  {
    // Get the field reference for the next style.
    CodeExpression right=new CodeFieldReferenceExpression(
                  new CodeTypeReferenceExpression(typeName),
                  items[i].Trim());

    // If this is the last style in the list...
    if (i+1 == items.Length)
    {
      // Then the right side of the expression is the 
      // last field reference.
      expr2.Right=right;
    }
    else
    {
      // Otherwise the right side of the 
      // expression is another binary 
      // operator...
      CodeBinaryOperatorExpression b2=
                  new CodeBinaryOperatorExpression();
      b2.Operator=CodeBinaryOperatorType.BitwiseOr;
      expr2.Right=b2;

      // and the left side of the binary 
      // operator is the field reference.
      b2.Left=right;

      // And we're all set to add the next 
      // style to the right of this 
      // expression.
      expr2=b2;
    }
  }

  return expr;
}

分配需要类型转换的值

接下来需要处理的复杂事情是为结构体(如 PointSize)分配“400, 190”这样的值。我为此选择了一种可扩展的机制,这样就可以生成特定于属性类型的 CodeDom。例如:

[AssignType("System.Drawing.Point")]
protected CodeExpression EvalPoint(string val)
{
  AddNamespace("System.Drawing");
  AddAssembly("System.Drawing.dll");
  string[] coord=val.Split(',');
  return new CodeObjectCreateExpression("Point",
  new CodeExpression[]
  {
    new CodePrimitiveExpression(Convert.ToInt32(coord[0].Trim())),
    new CodePrimitiveExpression(Convert.ToInt32(coord[1].Trim())),
  });
}

同样,这不一定是世界上最健壮的代码,但目前它能完成任务。

你会注意到修饰该方法的 AssignType 属性。代码生成器会查找带有标识属性类型的属性修饰的方法,如果找到适当的方法,它将调用该方法并允许该方法返回一个可用作赋值语句右值的 CodeExpression。这种方法让我可以根据优化良好的赋值来定制生成的代码。缺点是并非所有可能需要评估的 struct 和类都已实现。

简单类型

最后,非结构体的值类型从 string 转换为 property type

object cval=Converter.Convert(pea.Value, 
                   pea.PropertyInfo.PropertyType);
assignVal=new CodePrimitiveExpression(cval);

创建赋值语句

最后,构建赋值语句

CodeAssignStatement assign=
  new CodeAssignStatement(
     new CodePropertyReferenceExpression(
        new CodeVariableReferenceExpression(currentMember),
        pea.PropertyInfo.Name),
     assignVal);

事件分配

事件分配(将实例的方法与事件关联)实际上非常简单

private void OnAssignEvent(object sender, EventEventArgs eea)
{
  CodeAttachEventStatement assign=new CodeAttachEventStatement(
    new CodeEventReferenceExpression(
      new CodeVariableReferenceExpression(currentMember),
      eea.EventInfo.Name),
    new CodeDelegateCreateExpression(
      new CodeTypeReference(eea.EventInfo.EventHandlerType.FullName),
      new CodeVariableReferenceExpression(eea.SourceName),
      eea.MethodName));

  method.Statements.Add(assign);
  eea.Handled=true;
}

ISupportInitialize 和布局支持

实现 ISupportInitialize 的类应该调用 BeginInitEndInit。同样,实现 SuspendLayoutResumeLayout 的类(例如所有 Control 类)也应该调用这些方法,以便这些方法将任何属性赋值括起来。这是由以下代码处理的,并演示了 CodeDom 中的方法调用

private void OnBeginInitCheck(object sender, 
                     SupportInitializeEventArgs siea)
{
  // Check for ISupportInitialize interface
  TypeFilter filter=new TypeFilter(InterfaceFilter);
  Type[] interfaces=siea.Type.FindInterfaces(filter, 
     "System.ComponentModel.ISupportInitialize");
  if (interfaces.Length > 0)
  {
    AddNamespace("System.ComponentModel");
    AddAssembly("System.dll");
    CodeMethodInvokeExpression cmie=
         new CodeMethodInvokeExpression(
           new CodeVariableReferenceExpression(currentMember),
           "BeginInit", new CodeExpression[] {});
    method.Statements.Add(cmie);
  }

  // Check for SuspendLayout
  if (siea.Type.GetMethod("SuspendLayout") != null)
  {
    CodeMethodInvokeExpression cmie=
         new CodeMethodInvokeExpression(
             new CodeVariableReferenceExpression(currentMember),
             "SuspendLayout", new CodeExpression[] {});
    method.Statements.Add(cmie);
  }

  siea.Handled=true;
}

OnEndInitCheck 方法类似。

添加到集合

代码生成器假设将实例添加到实现集合(ICollectionIList)的属性是通过 Add 方法完成的。我这里采取的一个捷径是,如果属性是读写,那么它就*不是*集合,而是当前节点是一个专门的实例,被分配给祖父母节点的属性。如果属性*是*只读,那么代码生成器假设该属性是一个集合。要使这更健壮需要大量工作,并且是 MyXaml 解析器的一部分。目前的实现看起来像这样

private void OnAddToCollection(object sender, 
                              CollectionEventArgs cea)
{
  string parentMember=(string)currentMemberStack.ToArray()
     [currentMemberStack.Count-1];
  if (cea.PropertyInfo.CanWrite)
  {
    // We're going to assume this is a property 
    // assignment, since the property
    // is writeable, and .NET standards suggest 
    // that a collection property 
    // should be read-only. This will need to be 
    // refactored later on to be 
    // more robust.

    CodeAssignStatement assign=new CodeAssignStatement(
      new CodePropertyReferenceExpression(
        new CodeVariableReferenceExpression(parentMember),
        cea.PropertyInfo.Name),
      new CodeVariableReferenceExpression(currentMember));

    method.Statements.Add(assign);
  }
  else
  {
    // We're going to assume the property 
    // is a collection object.
    CodeMethodInvokeExpression cmie=new CodeMethodInvokeExpression(
      new CodePropertyReferenceExpression(
        new CodeVariableReferenceExpression(parentMember),
        cea.PropertyInfo.Name), 
      "Add",
      new CodeExpression[]
      {
        new CodeVariableReferenceExpression(currentMember),
      });
    method.Statements.Add(cmie);
  }

  cea.Handled=true;
}

注释

添加注释与某些其他过程相比微不足道。

private void OnComment(object sender, CommentEventArgs cea)
{
  // Append a blank line.
  method.Statements.Add(new CodeSnippetStatement(""));
  CodeCommentStatement ccs=new CodeCommentStatement(
                 new CodeComment(cea.Comment, false));
  method.Statements.Add(ccs);
}

代码

解压下载文件并导航到 Clifton.Tools.Xml 文件夹,您将在此处找到您要打开的解决方案文件 Clifton.Tools.sln

结论

能够将 XML 对象图编译成您最喜欢的 .NET 语言打开了各种可能性。在许多方面,由于 XML 的分层布局,在 XML 中可视化对象图比在代码中更容易,因此也更容易进行更改。XML 也是语言中立的,因此您可以声明性地编写对象图,然后使用您熟悉的任何语言处理命令式代码。它也比命令式代码更简洁(想象一下!)。然而,XML 的缺点众多——智能感知尚不完善,语法检查也缺乏,许多人习惯了命令式编程,当他们以声明性方式处理 XML 时会感到“情感”上的抵触。当然,直到现在,还存在性能和安全性问题,这些问题通过编译 XML 并生成程序集来解决,从而完全消除了解析器。嗯。等一下!

最后评论

如果您有兴趣为该项目做出贡献,请在下方留言,我将为您设置一个CVS存储库帐户。

© . All rights reserved.