博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一起谈.NET技术,从数据到代码—通过代码生成机制实现强类型编程[上篇]
阅读量:5752 次
发布时间:2019-06-18

本文共 9163 字,大约阅读时间需要 30 分钟。

  我不知道大家对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: 
2: 
3:   
4:   
5:   
6: 

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

  总的来说,BuildCodeObject方法的目的就是一个将XML转换成对象。首先在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最终体现的代码的结构,但是本身是不基于某种具体的编程语言的,也就是说是语言中性的。最终我们需要另一个对象将转换成基于某种编程的语言的代码:。

  在上面的代码中,我们利用上面定义的MessageCodeGenerator类型,将上述我们提到的包含消息定义的XML文件转换成对象。最终通过将其分别转换成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: // 
3: //     This code was generated by a tool.
4: //     Runtime Version:4.0.30319.1
5: //
6: //     Changes to this file may cause incorrect behavior and will be lost if
7: //     the code is regenerated.
8: // 
9: //------------------------------------------------------------------------------
10: 
11: namespace Artech.CodeDomGenerator {
12:
13:
14:     public class Messages {
15:
16:         public class Validation {
17:
18:             public static Artech.CodeDomGenerator.MessageEntry MandatoryField = new Artech.CodeDomGenerator.MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation");
19:
20:             public static Artech.CodeDomGenerator.MessageEntry GreaterThan = new Artech.CodeDomGenerator.MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation");
21:         }
22:
23:         public class Confirmation {
24:
25:             public static Artech.CodeDomGenerator.MessageEntry ReallyDelete = new Artech.CodeDomGenerator.MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation");
26:         }
27:     }
28: }

  下面是VB.NET代码:

1: '------------------------------------------------------------------------------
2: ' 
3: '     This code was generated by a tool.
4: '     Runtime Version:4.0.30319.1
5: '
6: '     Changes to this file may cause incorrect behavior and will be lost if
7: '     the code is regenerated.
8: ' 
9: '------------------------------------------------------------------------------
10: 
11: Option Strict Off
12: Option Explicit On
13: 
14: 
15: Namespace Artech.CodeDomGenerator
16:
17:     Public Class Messages
18:
19:         Public Class Validation
20:
21:             Public Shared MandatoryField As Artech.CodeDomGenerator.MessageEntry = New Artech.CodeDomGenerator.MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation")
22:
23:             Public Shared GreaterThan As Artech.CodeDomGenerator.MessageEntry = New Artech.CodeDomGenerator.MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation")
24:         End Class
25:
26:         Public Class Confirmation
27:
28:             Public Shared ReallyDelete As Artech.CodeDomGenerator.MessageEntry = New Artech.CodeDomGenerator.MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation")
29:         End Class
30:     End Class
31: End Namespace

  在中,我们将着重介绍如果通过VS的扩展实现如何将我们的MessageCodeGenerator和XML进行绑定,使XML内容改变的时候,相应的代码能够动态的生成。 

转载于:https://www.cnblogs.com/waw/archive/2011/09/01/2162759.html

你可能感兴趣的文章
[工具]前端自动化工具grunt+bower+yoman
查看>>
关于完成生鲜电商项目后的一点总结
查看>>
noip2012 普及组
查看>>
第二阶段 铁大Facebook——十天冲刺(10)
查看>>
Java判断是否为垃圾_Java GC如何判断对象是否为垃圾
查看>>
多项式前k项和java_多项式朴素贝叶斯softmax改变
查看>>
java数组只能交换0下标和n_编程练习-只用0交换排序数组
查看>>
centos7安装mysql视频教程_centos7安装mysql(完整)
查看>>
php图片赋值,php如何优雅地赋值
查看>>
【探索HTML5第二弹01】HTML5的前世今生以及来世
查看>>
Failed to connect to remote VM. Connection refused. Connection refused: connect
查看>>
freeze
查看>>
SAP HANA存储过程结果视图调用
查看>>
设计模式 ( 十八 ):State状态模式 -- 行为型
查看>>
OracleLinux安装说明
查看>>
nova分析(7)—— nova-scheduler
查看>>
Entity Framework 实体框架的形成之旅--Code First模式中使用 Fluent API 配置(6)
查看>>
OpenMediaVault 搭建git,ssh无法连接问题
查看>>
java多线程之:Java中的ReentrantLock和synchronized两种锁定机制的对比 (转载)
查看>>
【Web动画】SVG 实现复杂线条动画
查看>>