二、通过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的方式进行初始化。
{
public CodeCompileUnit BuildCodeObject(XmlDocument messages)
{
var codeObject = new CodeCompileUnit();
var codeNamespace = new CodeNamespace("Artech.CodeDomGenerator");
codeObject.Namespaces.Add(codeNamespace);
var codeType = new CodeTypeDeclaration("Messages");
codeNamespace.Types.Add(codeType);
GenerateCatetoryClasses(codeType, messages);
return codeObject;
}
private void GenerateCatetoryClasses(CodeTypeDeclaration root, XmlDocument messageDoc)
{
var messageEntries = messageDoc.GetElementsByTagName("message").Cast<XmlElement>();
var categories = (from element in messageEntries
select element.Attributes["category"].Value).Distinct();
foreach (var category in categories)
{
var categoryType = new CodeTypeDeclaration(category);
root.Members.Add(categoryType);
foreach (var element in messageDoc.GetElementsByTagName("message").Cast<XmlElement>().
Where(element => element.Attributes["category"].Value == category))
{
GenerateMessageProperty(element, categoryType);
}
}
}
private void GenerateMessageProperty(XmlElement messageEntry, CodeTypeDeclaration type)
{
string id = messageEntry.Attributes["id"].Value;
string value = messageEntry.Attributes["value"].Value;
string categotry = messageEntry.Attributes["category"].Value;
var field = new CodeMemberField(typeof(MessageEntry), id);
type.Members.Add(field);
field.Attributes = MemberAttributes.Public | MemberAttributes.Static;
field.InitExpression = new CodeObjectCreateExpression(
typeof(MessageEntry),
new CodePrimitiveExpression(id),
new CodePrimitiveExpression(value),
new CodePrimitiveExpression(categotry));
}
}
三、通过CodeDomProvider转化给予某种语言的代码
CodeCompileUnit最终体现的代码的结构,但是CodeCompileUnit本身是不基于某种具体的编程语言的,也就是说CodeCompileUnit是语言中性的。最终我们需要另一个对象将CodeCompileUnit转换成基于某种编程的语言的代码:CodeDomProvider。
在上面的代码中,我们利用上面定义的MessageCodeGenerator类型,将上述我们提到的包含消息定义的XML文件转换成CodeDomProvider对象。最终通过CodeDomProvider将其分别转换成C#代码和VB。NET代码。
var messageDoc = new XmlDocument();
messageDoc.Load("Messages.xml");
var codeObject = generator.BuildCodeObject(messageDoc);
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CodeGeneratorOptions options = new CodeGeneratorOptions();
using (StreamWriter writer = new StreamWriter("messages.cs"))
{
provider.GenerateCodeFromCompileUnit(codeObject, writer, options);
}
provider = CodeDomProvider.CreateProvider("VisualBasic");
using (StreamWriter writer = new StreamWriter("messages.vb"))
{
provider.GenerateCodeFromCompileUnit(codeObject, writer, options);
}
Process.Start("messages.cs");
Process.Start("messages.vb");