现在的位置: 首页 > 综合 > 正文

代码生成器与 .NET

2013年10月07日 ⁄ 综合 ⁄ 共 5775字 ⁄ 字号 评论关闭

代码生成器与 .NET

发布日期: 12/23/2004 | 更新日期: 12/23/2004

Pierre Couzy
WinWise

摘要:代码生成器是您日常生活中的一部分,即使您没有意识到这一点。Pierre Couzy 说明了如何在项目中利用它们。

*

本页内容
简介 简介
工具和示例 工具和示例
版本控制 版本控制
小结 小结

简介

假设您在一家由 DBA 统治一切的公司里工作:您不能生成只是“到 Oracle 那里去取一些记录”的应用程序。您只能依靠存储过程,因为在该级别存在安全层。

生成应用程序通常涉及到下列步骤:

1.

创建一批存储过程。

2.

创建能够与存储过程通信的 C# 类。

3.

创建将管理 Microsoft ASP.NET 表单或 Microsoft Windows 窗体或者形成另一个层的更高级别的类。

步骤 1 和步骤 2 是密切相关的:它们共享大量结构,它们消耗另一个步骤所产生的东西,等等。问题在于,您拥有两种不同的语言(在该示例中,为 PL-SQL 和 C#),并且无法从其中一种语言引用另一种语言。

您说这没有什么大不了的?团队中的第一个开发人员将使一切保持正常,因为他了解 .NET、PL/SQL 以及安全模型的本质。他可能将编写两个东西:首先是一组帮助器类,然后是一个有效的示例 — 于是,其他开发人员将使用这些帮助器类,复制/粘贴有效示例,并针对他们的需要进行修改。

如果您已经在软件行业中工作了足够长的时间,就会知道接下来将会发生什么事情:随着要求的增加,帮助器类将慢慢变大,并且其中一些将很快过时。当然,没有人敢于修改它们,因为这可能会毁坏较旧的项目。同时,开发人员将重用不再与新规则同步的有效示例,并且他们将没有办法了解哪些部分仍在使用,以及哪些部分可以毫无风险地丢弃。您将陷入一片混乱之中(复制/粘贴编程)。

有很多克服这一复杂性的办法,而我将介绍您可能已经忽视的一种有用的办法 — 代码生成器。其实您已经使用了它们,即使您没有意识到这一点:每当您创建一个新的 ASPX 页时,隐藏机制就会将它转换为 C# 或 Microsoft Visual Basic 类。当您在项目中引用 COM 组件、Web 服务甚至数据结构(类型化数据集)时,会发生相同的事情 — 有一个类被自动生成,它隐藏了您不希望了解的复杂性。

工具和示例

您将遇到的第一批代码生成器只是一些隐藏复杂性的工具。它们不允许您解释如何生成代码,并且不让您随后更改生成的代码(它们将清除您进行的更新)。

例如,“Add Web Reference Wizard”甚至不向您显示插入到项目中的代码(除非您请求它显示)。

图 1. 显示为 Web 引用生成的代码

Windows 窗体设计器也是一个代码生成器。在这里,您可以查看代码,但最好不要动它。

     #region Windows Form Designer generated code
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent()
      {
         this.button1 = new System.Windows.Forms.Button();
         this.SuspendLayout();

让我们更进一步地探讨该问题:代码生成器可以提供格式规范的主干,以供您随后添加自己的代码。您可能知道 Reflector,但您是否知道 Reflector 的一些外接程序 (http://www.dotnetwiki.org/Default.aspx?tabid=52) 包含代码生成器?它们使您可以执行以下工作:

图 2. 使用 Reflector 生成代码

当您按下“Generate”按钮时,将创建一个新文件:

// Generated by Refly
namespace MyTestNameSpace
{
    using System;
    
    
    /// <summary>Test fixture for the <see cref="SomeBusinessClass"/> class
    ///</summary>
    /// <remarks />
    [TestFixture()]
    public class SomeBusinessClassTest
    {
        
        /// <summary />
        /// <remarks />
        private SomeBusinessClass _someBusinessClass = null;
        
        /// <summary />
        /// <remarks />
        public virtual SomeBusinessClass SomeBusinessClass
        {
            get
            {
                return this._someBusinessClass;
            }
        }
        
        /// <summary>Tests the EstimateSomeNumber method</summary>
        /// <remarks>
        ///   <para>Test coverage (estimated): 100,0%</para>
        ///   <para>Target path:</para>
        ///   <code>/* 0 */ return 0x2a;
        /// </code>
        /// </remarks>
        [Test()]
        [Ignore()]
        public virtual void EstimateSomeNumber0()
        {
            throw new System.NotImplementedException();
        }
        
        /// <summary>Tests the IsSomethingAlreadyInDataBase method</summary>
        /// <remarks>
        ///   <para>Test coverage (estimated): 100,0%</para>
        ///   <para>Target path:</para>
        ///   <code>/* 0 */ return false;
        /// </code>
        /// </remarks>
        [Test()]
        [Ignore()]
        public virtual void IsSomethingAlreadyInDataBase0()
        {
            throw new System.NotImplementedException();
        }
        
        /// <summary>Sets up the fixture</summary>
        /// <remarks />
        [SetUp()]
        public virtual void SetUp()
        {
            throw new System.NotImplementedException();
        }
        
        /// <summary>Releases resource allocated in the fixture</summary>
        /// <remarks />
        [TearDown()]
        public virtual void TearDown()
        {
            throw new System.NotImplementedException();
        }
    }
}

您不了解有关“单元测试”的任何内容,但是您仍然能够生成有效的测试类。与上一个示例的不同之处在于,这一次您必须在生成的类的内部编码。可是,我们仍然无法创建我们自己的模板。

下一个步骤是创建我们自己的代码生成器。我们需要的全部东西就是一种根据一组常用信息(例如,将要获得的列的名称、要用来请求信息的方式、要允许的事务种类等等)来生成文本文件(C#、PL-SQL 等等)的方式。当然,您无法生成所有东西,因此需要一个能够生成自定义主干的工具,而某个开发人员随后将添加特定的实现细节。

该工具可以是 Perl、Microsoft VBScript 或普通的旧式 ASP(就本文而言)— 毕竟,它是一种可以根据参数生成文本文件的很好的工具。您可能使用 ASP 生成 HTML 文件,但是它还适用于生成 Microsoft Excel 或 CSV、WML — 因此,为什么不用它来生成 T-SQL 或 C# 呢?

您的 ASP 文件可能如下所示:

<%@ Language=VBScript %>
<%if request("Generate").count = 0 then%>
<HTML><BODY>
   <form method=post>
   <P>Property name :<INPUT name=PropertyName></P>
   <P>What is the type of your property ? <INPUT name=PropertyType></P>
   <P>Read only <input type=checkbox name=ReadOnly value=true></P>
   <P><INPUT type=submit value="Generate !" name=Generate></P>
   </form>
</BODY></HTML>
<% else 
dim PropertyName, PropertyType, ReadOnly, PropertyModifier
PropertyName = Request("PropertyName")
PropertyType = Request("PropertyType")
ReadOnly = (Request("ReadOnly")="true")
if ReadOnly then PropertyModifier = "ReadOnly" else PropertyModifier = ""
Response.ContentType = "text/plain"
%>

Private _<%=PropertyName%> as <%=PropertyType%>

Public <%=propertyModifier%> Property <%=PropertyName%> as <%=PropertyType%>

   Get
      Return _<%=PropertyName%>
   End Get
<%if not ReadOnly then%>
   Set (ByVal Value as <%=PropertyType%>)
      _<%=PropertyName%> = Value
   End Set
<%end if%>
End Property
<%end if%>

当您执行该文件时,您将获得如下所示的内容:

图 3. 生成的 Web 页

图 4. 生成的代码

您还可以在 Internet 上找到代码生成器。我通常使用 CodeSmith;它是免费的并且易于理解,但是您可以找到很多其他的代码生成器。图 5 是一个屏幕截图,它显示了一种等待泛型的聪明方式;它生成一个强类型的哈希表。

图 5. CodeSmith

这些工具通常接受两个输入 — 一个模板文件(像我们的 ASP 文件)和一个 XML 参数文件(像表单的内容),并产生一个新文件。

开发人员被赋予这些模板,并且他们通常通过同一组参数来使用两个不同的模板。这样,他们将相同的信息给予这两个模板,并且获得共享信息的一个 T-SQL 文件和一个 C# 文件,同时无需承担遗忘或键入错误的风险;这样就为您完成了此工作的乏味部分。

版本控制

当然,在使用模板和自动生成的代码时,事情会变得复杂。在现实生活中,模板会演化(例如,您可能希望赋予 C# 类一种调用某些存储过程的异步方式);如果是这样,那么您需要一种相应的办法,以应付开发人员在主干生成之后放入的实现。

为此,您必须确保开发人员添加的代码独立于您的模板所生成的代码。实现此目的最简单的技术是创建两个不同的类:一个用于生成的代码,另一个从第一个继承,但将不生成。

图 6. 对生成的代码进行版本控制

由于该技术所具有的继承机制,因此它在您生成 .NET 代码时非常适用;但是,如果您要生成其他种类的代码(VBScript、SQL),则将无法以这种方式工作。在那种情况下,您主要依靠自己来解决问题。我使用的机制很简单:我在生成的代码中提供了占位符(在 C# 中为区域;在 SQL 中为特定注释所围绕的块),并且当代码被重新生成时,只有这些区域中的代码被保留。传输自定义代码本身同样简单:模板始终接受 PreviousFile 参数。当该参数存在时,将分析以前的文件以获得自定义代码,并将其重新插入到当前的生成文件中。

如果没有办法恢复原样,则千万不要弄乱生成的代码:您将发现自己正在进行复制/粘贴编程。

小结

下面是在使用代码生成器时需要记住的一些事情:

如果不打算对生成的代码进行修改,请预先声明。

不要忘记,开发人员没有时间了解生成代码的内部结构,因此请为他们提供帮助,并且尽可能详细地对入口点进行说明。

考虑进行版本控制,并明确说明您将允许人们做什么。或许您希望开发人员只在代码的特定区域中添加自定义代码,或者您希望他们从您的类继承。

模板的新版本应当始终能够重新生成旧版本所生成的代码。如果新版本需要比旧版本更多的代码以便正确工作,则请将需要代码的位置变得明显一些,并且插入一些能够生成错误的代码(在编译时 — 如果可能的话)。

如果您决定中断模板的版本控制机制(例如,通过添加在应用新版本时无法恢复原样的代码),请保留该模板和您使用的参数的副本。如果您需要为某个旧项目生成另一个类似的代码文件,并且您已经丢失了在开发该旧项目时使用的模板版本,则会发生很糟糕的事情。我只是将模板和参数与项目一起放在 SourceSafe 中。

在发布模板之前,对其进行彻底的测试。模板会在开发人员之间飞快地传播,因为他们无需多少知识就可以解决问题。如果更新生成的文件意味着丢失自定义,那么程序错误将很难查找,并且更加难以修复。

使用该方法能够走多远?简单说来,代码生成器使开发人员可以获得一个抽象级别。他不必将精力集中于技术过程(因为该过程已嵌入到模板中),并且可以考虑集成该过程。它使开发人员能够更好地理解客户需求,并使技术过程拥有更好的质量。Microsoft Visual Studio 已经广泛使用了这些技术,并且即将问世的版本已经添加了很多改进(引人注目的是一个类建模程序和不完整类机制,可用于将生成的代码与自定义代码分隔开)。

现实世界中的 .NET

Pierre Couzy 是一个专门研究分布式系统体系结构的培训师和顾问。作为 20 余部书籍的作者,他目前被 WinWise 聘为 ASP.NET 和 BizTalk 专家。他自 2003 年以来一直是地区主管,并且在法国的许多重要会议上发表了演讲。如果您要与他讨论法国爵士乐、桥牌(是的,就是那种简单的游戏)甚至计算机,请通过 Pierre.couzy@winwise.fr 与他联系。

转到原英文页面

抱歉!评论已关闭.