从数据到代码通过代码生成机制实现强类型编程[上篇]

  我不知道大家对CodeDOM的代码生成机制是否熟悉,但是有一点可以确定:如果你使用过Visual Studio,你就应该体验过它带给我们在编程上的便利。随便列举三种典型的代码生成的场景:在创建强类型DataSet的时候,VS会自动根据Schema生成相应的C#或者VB.NET代码;当我们编辑Resource文件的时候,相应的的后台代码也会自动生成;当我们通过添加Web Reference调用Web Service或者WCF Service的时候,VS会自动生成服务代理的代码和相应的配置。总的来说,通过和VS集成的动态代码生成工具使我们可以“强类型”的方式进行编程,进而提供我们的效率并减低错误的几率。

  实际上,除了VS提供的这些典型的代码生成场景中,我们可以根据需要开发一些自定义代码生成器,并且通过VS的扩展实现后台代码的实时生成,从而实现强类型编程的目的,现在我们举一个典型的应用场景——消息管理。

  一、一个典型的自定义代码生成器应用场景——消息管理

  无论对于怎么样的应用,我们都需要维护一系列的消息。消息的类型很多,比如验证消息、确认消息、日志消息等。我们一般会将消息储存在一个文件或者数据库中进行维护,并提供一些API来获取相应的消息项。这些API一般都是基于消息的ID来获取的,换句话说,消息获取的方式是以一种“弱类型”的编程方式实现的。如果我们能够根据消息存储的内容动态地生成相应的C#或者VB.NET代码,那么我们就能够以一种强类型的方式来获取相应的消息项了。

  比如说,现在我们定义了如下一个MessageEntry类型来表示一个消息条目。为了简单,我们尽量简化MessageEntry的定义,仅仅保留三个属性Id、Value和Category。Category表示该消息条目所属的类型,你可以根据具体的需要对其分类(比如根据模块名称或者Severity等)。Value是一个消息真实的内容,可以包含一些占位符({0},{1},…{N})。通过指定占位符对用的值,最中格式化后的文本通过Format返回。

1: public class MessageEntry 2: { 3: public string Id { get; private set; } 4: public string Value { get; private set; } 5: public string Category { get; private set; } 6: public MessageEntry(string id, string value, string category) 7: { 8: this.Id = id; 9: this.Value = value; 10: this.Category = category; 11: } 12: public string Format(params object[] args) 13: { 14: return string.Format(this.Value, args); 15: } 16: }

  现在我们所有的消息定义在如下一个XML文件中,<message>XML元素代码一个具体的MessageEntry,相应的属性(Attribute)和MessageEntry的属性(Property)相对应。

1: <?xml version="1.0" encoding="utf-8" ?> 2: <messages> 3: <message id="MandatoryField" value="The {0} is mandatory." category="Validation"/> 4: <message id="GreaterThan" value="The {0} must be greater than {1}." category="Validation"/> 5: <message id="ReallyDelete" value="Do you really want to delete the {0}." category="Confirmation"/> 6: </messages>

  在上面的XML中,定义了两个类别(Validation和Confirmation)的三条MessageEntry。我们需要通过我们的代码生成工具生成一个包含如下C#代码的CS文件。

1: namespace Artech.CodeDomGenerator 2: { 3: public class Messages 4: { 5: public class Validation 6: { 7: public static Artech.CodeDomGenerator.MessageEntry MandatoryField = new Artech.CodeDomGenerator.MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation"); 8: public static Artech.CodeDomGenerator.MessageEntry GreaterThan = new Artech.CodeDomGenerator.MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation"); 9: } 10: public class Confirmation 11: { 12: public static Artech.CodeDomGenerator.MessageEntry ReallyDelete = new Artech.CodeDomGenerator.MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation"); 13: } 14: } 15: }

  那么我们就能够直接通过生成出来的Messages类,以强类型的方式获取并格式化每一条MessageEntry的内容了。

1: Console.WriteLine(Messages.Validation.MandatoryField.Format("User Name")); 2: Console.WriteLine(Messages.Validation.GreaterThan.Format("Age",18)); 3: Console.WriteLine(Messages.Confirmation.ReallyDelete.Format("Order record"));

  下面是输出结果:

1: The User Name is mandatory. 2: The Age must be greater than 18. 3: Do you really want to delete the Order record.

  要实现上面的功能实际上包含两个步骤:一是动态解析包含消息定义的XML文件,并生成我们希望结构的一个代码定义,而是通过和VS进行集成,借助VS自定义工具将前面生成的内容真正写入到一个具体的.cs文件中。第一个步骤可以通过CodeDOM轻松实现,而第二个步骤借助于VS的扩展也会很简单。本篇文章我们只关注第一个方面,下面我们在对第二个方面进行介绍。

  二、通过CodeDom实现动态代码生成

  CodeDOM 提供了表示许多常见的源代码元素类型的类型。您可以设计一个生成源代码模型的程序,使用CodeDOM 元素构成一个对象图。而这个对象图包含C#或者VB.NET代码包含的基本元素:命名空间、类型、类型成员(方法、属性、构造函数、事件等),并且包括方法实现的具体语句(Statement)。也就是说它的结构就是对一个具体.vb或者.cs文件代码的反映。在这里我不会具体介绍CodeDOM体系结构,有兴趣的读者可以参与MSDN官方文档。

  CodeDOM最终体现出来的是一个叫做CodeCompileUnit对象,这个对象通过如下定义的MessageCodeGnerator的BuildCodeObject方法返回。下面给出了生成CodeCompileUnit的全部实现,即使你对CodeDOM完全不了解,结合上面给出的保存消息的XML和我们最终期望的C#代码的结构,相信也能够看懂整个实现逻辑。

  总的来说,BuildCodeObject方法的目的就是一个将XML转换成CodeCompileUnit对象。首先在BuildCodeObject方法中,添加了一个命名空间(Artech.CodeDomGenerator),并在该命名空间中定义了一个Messages的类。在Messages类会为每一个消息类别定义一个嵌套类,类型的名称就是消息类别的名称(比如Validation、Confirmation等)。我们具体的MessageEntry通过公共静态属性的形式进行定义,并且采用Inline的方式进行初始化。

1: public class MessageCodeGenerator

2: {

3: public CodeCompileUnit BuildCodeObject(XmlDocument messages)

4: {

5: var codeObject = new CodeCompileUnit();

6: var codeNamespace = new CodeNamespace("Artech.CodeDomGenerator");

7: codeObject.Namespaces.Add(codeNamespace);

8: var codeType = new CodeTypeDeclaration("Messages");

9: codeNamespace.Types.Add(codeType);

10: GenerateCatetoryClasses(codeType, messages);

11: return codeObject;

12: }

13:

14: private void GenerateCatetoryClasses(CodeTypeDeclaration root, XmlDocument messageDoc)

15: {

16: var messageEntries = messageDoc.GetElementsByTagName("message").Cast<XmlElement>();

17: var categories = (from element in messageEntries

18: select element.Attributes["category"].Value).Distinct();

19:

20: foreach (var category in categories)

21: {

22: var categoryType = new CodeTypeDeclaration(category);

23: root.Members.Add(categoryType);

24:

25: foreach (var element in messageDoc.GetElementsByTagName("message").Cast<XmlElement>().

26: Where(element => element.Attributes["category"].Value == category))

27: {

28: GenerateMessageProperty(element, categoryType);

29: }

30: }

31: }

32:

33: private void GenerateMessageProperty(XmlElement messageEntry, CodeTypeDeclaration type)

34: {

35: string id = messageEntry.Attributes["id"].Value;

36: string value = messageEntry.Attributes["value"].Value;

37: string categotry = messageEntry.Attributes["category"].Value;

38:

39: var field = new CodeMemberField(typeof(MessageEntry), id);

40: type.Members.Add(field);

41: field.Attributes = MemberAttributes.Public | MemberAttributes.Static;

42: field.InitExpression = new CodeObjectCreateExpression(

43: typeof(MessageEntry),

44: new CodePrimitiveExpression(id),

45: new CodePrimitiveExpression(value),

46: new CodePrimitiveExpression(categotry));

47: }

48: }

  三、通过CodeDomProvider转化给予某种语言的代码

  CodeCompileUnit最终体现的代码的结构,但是CodeCompileUnit本身是不基于某种具体的编程语言的,也就是说CodeCompileUnit是语言中性的。最终我们需要另一个对象将CodeCompileUnit转换成基于某种编程的语言的代码:CodeDomProvider。

  在上面的代码中,我们利用上面定义的MessageCodeGenerator类型,将上述我们提到的包含消息定义的XML文件转换成CodeDomProvider对象。最终通过CodeDomProvider将其分别转换成C#代码和VB。NET代码。

1: var generator = new MessageCodeGenerator(); 2: var messageDoc = new XmlDocument(); 3: messageDoc.Load("Messages.xml"); 4: var codeObject = generator.BuildCodeObject(messageDoc); 5: CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp"); 6: CodeGeneratorOptions options = new CodeGeneratorOptions(); 7: using (StreamWriter writer = new StreamWriter("messages.cs")) 8: { 9: provider.GenerateCodeFromCompileUnit(codeObject, writer, options); 10: } 11:  12: provider = CodeDomProvider.CreateProvider("VisualBasic"); 13: using (StreamWriter writer = new StreamWriter("messages.vb")) 14: { 15: provider.GenerateCodeFromCompileUnit(codeObject, writer, options); 16: } 17:  18: Process.Start("messages.cs"); 19: Process.Start("messages.vb");

  这是C#代码(和我们开始提到过的完全一致):

1: //------------------------------------------------------------------------------ 2: // <auto-generated> 3: // This code was generated by a tool. 4: // Runtime Version:4.0.30319.1 5: //<

时间: 2024-10-12 20:50:35

从数据到代码通过代码生成机制实现强类型编程[上篇]的相关文章

从数据到代码——通过代码生成机制实现强类型编程[下篇]

在<上篇>中,我们实现了将保存有消息条目的XML向CodeDOM的转换,即是将XML文件生成一个CodeCompileUnit对象,而该CodeCompileUnit对象反映出来的DOM层次和我们将会生成的代码文件向匹配.在下篇中,我们将实现整个代码生成系统的第二个步骤--通过VS的Custom Tool实现数据(保存消息条目的XML)向代码文件的自动转换. 一.让MessageCodeGenerator继承BaseCodeGeneratorWithSite 在<上篇>我们创建了M

一起谈.NET技术,从数据到代码—通过代码生成机制实现强类型编程[下篇]

在<上篇>中,我们实现了将保存有消息条目的XML向CodeDOM的转换,即是将XML文件生成一个CodeCompileUnit对象,而该CodeCompileUnit对象反映出来的DOM层次和我们将会生成的代码文件向匹配.在下篇中,我们将实现整个代码生成系统的第二个步骤--通过VS的Custom Tool实现数据(保存消息条目的XML)向代码文件的自动转换. 一.让MessageCodeGenerator继承BaseCodeGeneratorWithSite 在<上篇>我们创建了M

从数据到代码通过代码生成机制实现强类型编程[下篇]

在<上篇>中,我们实现了将保存有消息条目的XML向CodeDOM的转换,即是将XML文件生成一个CodeCompileUnit对象,而该CodeCompileUnit对象反映出来的DOM层次和我们将会生成的代码文件向匹配.在下篇中,我们将实现整个代码生成系统的第二个步骤--通过VS的Custom Tool实现数据(保存消息条目的XML)向代码文件的自动转换. 一.让MessageCodeGenerator继承BaseCodeGeneratorWithSite 在<上篇>我们创建了M

通过代码生成机制实现强类型编程-CodeSimth版

        一直想写一个Code生成系列,但写到CodeSimth,发觉在TerryLee 和努力学习的小熊 两位大牛的博客里讲很详尽,所以就像写些示例方面的,但是苦于没有想到写些什么.最近Artech写了两篇从数据到代码--通过代码生成机制实现强类型编程--上篇和下篇,大牛写得是CodeDom的,今天我就想借借大牛的示例写个CodeSimth版的,希望Artech不要怪我,呵呵.我的Code生成技术已经写了CodeDom的见CodeDom系列目录,欢迎各位园友指教.         好直接

从数据到代码——基于T4的代码生成方式

在之前写一篇文章<从数据到代码>(上篇.下篇)中,我通过基于CodeDOM+Custom Tool的代码生成方式实现了将一个XML表示的消息列表转换成了相应的C#代码,从而达到了强类型编程的目的.实际上,我们最常用的代码生成当时不是CodeDOM,而是T4,这是一个更为强大,并且适用范围更广的代码生成技术.今天,我将相同的例子通过T4的方式再实现一次,希望为那些对T4不了解的读者带来一些启示.同时这篇文章将作为后续文章的引子,在此之后,我将通过两篇文章通过具体实例的形式讲述如果在项目将T4为我

一起谈.NET技术,从数据到代码—基于T4的代码生成方式

在之前写一篇文章<从数据到代码>(上篇.下篇)中,我通过基于CodeDOM+Custom Tool的代码生成方式实现了将一个XML表示的消息列表转换成了相应的C#代码,从而达到了强类型编程的目的.实际上,我们最常用的代码生成当时不是CodeDOM,而是T4,这是一个更为强大,并且适用范围更广的代码生成技术.今天,我将相同的例子通过T4的方式再实现一次,希望为那些对T4不了解的读者带来一些启示.同时这篇文章将作为后续文章的引子,在此之后,我将通过两篇文章通过具体实例的形式讲述如果在项目将T4为我

如何使用 Feed4JUnit 进行数据与代码分离的 Java 单元测试

Feed4JUnit 与 JUnit 经常,在应用程序的业务逻辑中存在大量的这样的接口:他们接受不同的输入,然后进行 或验证,或处理,进而完成相同的流程.比如网站的登录入口,用户名和密码都有长度的限制,同时也具有是否允许特殊字 符的限制等,所以在我们进行其单元测试的过程中,根据不同长度的用户名和密码,以及不同的字符组合,只需要提供相同 的测试代码结构,就能完成测试,不同的仅仅测试数据与期望值,但是因为每一个测试方法中的输入参数不同,我们必须为 每一个输入组编写单独的测试用例,从而产生大量冗余代码

框架-JAVA问题:删除数据库中数据的代码,测试不会报错,但实际什么都删不了

问题描述 JAVA问题:删除数据库中数据的代码,测试不会报错,但实际什么都删不了 这些操作做完后,数据库里的t_product表中,id=2的数据依然在,不是刷新的问题,刷新也还在 解决方案 为什么你的三个函数保存.修改.删除方法调用sql的时候都没有传递sql参数值呢? 解决方案二: 调用mapper里面的sql代码的时候,把要删除的id传进去了吗?不应该是sqlsession.delete("",参数);吗? 解决方案三: 参数没带.sqlsession.delect带上id 解决

数据即代码:元驱动编程

Coolshell.cn (感谢 @文艺复兴记(todd) 投递此文) 几个小伙伴在考虑下面这个各个语言都会遇到的问题: 问题:设计一个命令行参数解析API 一个好的命令行参数解析库一般涉及到这几个常见的方面: 1) 支持方便地生成帮助信息 2) 支持子命令,比如:git包含了push, pull, commit等多种子命令 3) 支持单字符选项.多字符选项.标志选项.参数选项等多种选项和位置参数 4) 支持选项默认值,比如:–port选项若未指定认为5037 5) 支持使用模式,比如:tar命