CodeDom 助手






4.84/5 (25投票s)
2007年9月21日
4分钟阅读

153421

6666
通过解析 C# 或 VB 来生成 CodeDom 代码

引言
我觉得编写 CodeDom 代码就像编写汇编语言。这就像用剪刀割草,或者用勺子往卡车上装沙子。它冗长,可以完成,但很繁琐。然而,使用 CodeDom 也有其优点。
现在,如果你能用 VB 或 C# 快速编写一个类并生成 CodeDom 代码,那将是件好事。CodeDom 助手借助 SharpDevelop 的 NRefactory 库,并结合我们自己编写的 CodeDomProvider
来生成 C# CodeDom 代码。你得到的代码将构建一个 CodeDom compileunit,而且肯定会很丑陋。但你能期望机器做出什么呢?
背景
代码生成可以通过多种技术完成。本质上,代码生成可以节省时间,并使你能够使用既定的模式来创建代码。
在 .NET 世界中,一种技术是使用 CodeDom。它允许你创建一个代码的文档对象模型。这可以被编译成程序集,也可以用于生成代码。每个 .NET 语言都应该有一个 CodeDomProvider
的实现来生成代码。问题是 CodeDom 并不实现每种语言的每一个语法构造。本质上,它是一个子集,但有足够的操作来模拟大多数语言构造。
解析 C# 或 VB 将是最困难的部分,但幸运的是,SharpDevelop 的一个小型库是 NRefactory。它拥有解析 C# 或 VB 代码所需的机制。对我们来说,关键组件是 CodeDomVisitor
类。遗憾的是,它只是一个部分实现。他们实现的足以运行 SharpDevelop 的窗体生成。我并不是说我完成了实现,而是说,我填补了 CodeDomVisitor
中大部分的空白。
第二部分是实现一个 CodeDomCodeProvider
。它将获取一个 CodeDom compileunit 并生成 C# 代码,用于创建 CodeDom compileunit。
不支持的 CodeDom 构造的替代方案
在将 C# 转换为 CodeDom 时,某些构造无法直接映射到 CodeDom 元素。我们必须模拟代码的意图。我敢肯定可以做出更好的转换,但这些对于我的需求来说已经足够了。例如:int a = b++;
肯定不等同于转换后的 CodeDom int a = (b = (b + 1));
。
并非所有构造都能完美地转换为 CodeDom;它们仅仅对我的需求来说足够了。最初用 C# 编写代码时,我已将这些限制考虑在内。例如
foreach
语句是通过 for
迭代器和 GetEnumerator()
来处理的。看这个
foreach (string s in mylist)
{
}
可以在 CodeDom 中构造为
for (System.Collections.IEnumerator _it1 = mylist.GetEnumerator();
_it1.MoveNext(); )
{
string s = ((string)_it1.Current);
}
do
语句是通过 for
迭代器来处理的。例如
do
{
}
while (expr);
可以在 CodeDom 中构造为
for (bool _do1 = true; _do; _do = expr)
{
}
while
语句再次通过 for
迭代器来处理。例如
while (expr)
{
}
可以在 CodeDom 中构造为
for (; expr; )
{
}
continue
和 break
语句是通过 goto
语句来处理的。例如
for (int i = 0; i < 5; i++)
{
if (i < 2)
continue;
if (i == 3)
break;
}
可以在 CodeDom 中构造为
for (int i = 0; i < 5; i = i + 1)
{
if (i < 2)
goto continue1;
if (i == 3)
goto break1;
continue1:
}
break1:
一元运算符:i++; ++i; i--; --i; !;
可以使用二元运算符构造:i = i + 1;
等等。
switch
语句变成了一个嵌套的 if
语句。
switch(expr)
{
case label1:
break;
case label2:
break;
default:
}
可以在 CodeDom 中构造为
object _switch1 = expr;
if (_switch1.Equals(label1))
{
}
else
{
if (_switch1.Equals(label2))
{
}
else
{
}
}
using(new a()) {}
语句可以通过以下方式模拟
object _dispose1 = null;
try
{
_dispose1 = new a();
}
finally
{
if (((_dispose1 != null)
&& (typeof(System.IDisposable).IsInstanceOfType(_dispose1) == true)))
{
(System.IDisposable)(_dispose1)).Dispose();
}
}
使用 NRefactory 构建 CodeDom
生成函数使用 NRefactory 解析器。NRefactory 支持 C# 和 VB。我倾向于使用 C#,没有测试过 VB。OutputClass
本质上就是输出组合框的选择。有原生的 C# 和 VB NRefactory 输出转换,这些比 CodeDom 更完整。对于其他语言,我们使用 CodeDomVisitor
,它将 NRefactory 解析器的输出映射到 CodeDom。然后将 CodeDom CodeCompileUnit
传递给 CodeDomProvider
,后者生成输出。
void Generate(ICSharpCode.NRefactory.SupportedLanguage language,
TextReader inputstream, OutputClass output)
{
ICSharpCode.NRefactory.IParser parser = ParserFactory.CreateParser(
language, inputstream);
parser.Parse();
if (parser.Errors.Count > 0)
{
ICSharpCode.Core.ExceptionDialog dlg =
new ICSharpCode.Core.ExceptionDialog(
null, "Error Parsing Input Code");
dlg.ShowDialog();
return;
}
if (output.CodeDomProvider != null)
{
CodeDomVisitor visit = new CodeDomVisitor();
visit.VisitCompilationUnit(parser.CompilationUnit, null);
// Remove Unsed Namespaces
for (int i = visit.codeCompileUnit.Namespaces.Count - 1; i >= 0; i--)
{
if (visit.codeCompileUnit.Namespaces[i].Types.Count == 0)
{
visit.codeCompileUnit.Namespaces.RemoveAt(i);
}
}
CodeGeneratorOptions codegenopt = new CodeGeneratorOptions();
codegenopt.BlankLinesBetweenMembers = true;
System.IO.StringWriter sw = new System.IO.StringWriter();
output.CodeDomProvider.GenerateCodeFromCompileUnit(
visit.codeCompileUnit, sw, codegenopt);
this.scintillaOutput.Text = sw.ToString();
sw.Close();
}
else
{
AbstractAstTransformer transformer = output.CreateTransformer();
// do what SharpDevelop does...
List<ISpecial> specials =
parser.Lexer.SpecialTracker.CurrentSpecials;
if (language == SupportedLanguage.CSharp &&
transformer is ToVBNetConvertVisitor)
{
PreprocessingDirective.CSharpToVB(specials);
}
else if (language == SupportedLanguage.VBNet &&
transformer is ToCSharpConvertVisitor)
{
PreprocessingDirective.VBToCSharp(specials);
}
parser.CompilationUnit.AcceptVisitor(transformer, null);
IOutputAstVisitor prettyprinter = output.CreatePrettyPrinter();
using (SpecialNodesInserter.Install(specials, prettyprinter))
{
prettyprinter.VisitCompilationUnit(parser.CompilationUnit, null);
}
this.scintillaOutput.Text = prettyprinter.Text;
}
}
NRefactory 的 CodeDomVisitor
类需要更新以实现更多的 C# 构造。我确信我遗漏了一些。
生成 C# 代码 CodeDom
所有 CodeDom compileunit 都可以传递给任何 CodeDomProvider
来生成代码。因此,我们编写了一个新的 CodeDom provider 来为我们生成 CodeDom 代码。实现非常简单。CodeDomCodeProvider
的主要工作是创建一个 ICodeGenerator
。
public class CodeDomCodeProvider : System.CodeDom.Compiler.CodeDomProvider
{
public CodeDomCodeProvider()
: base()
{
}
public override string FileExtension
{
get
{
return "cs";
}
}
public override System.CodeDom.Compiler.ICodeGenerator CreateGenerator()
{
return new CodeGenerator();
}
public override System.CodeDom.Compiler.ICodeCompiler CreateCompiler()
{
return null;
}
}
CodeGenerator
然后遍历 CodeDom 元素并输出代码,这些代码将在编译时重建树。
使用其他 CodeDomProviders
在初始化时,CodeDom 助手会扫描 GAC 以查找具有 CodeDomProvider 实现的程序集。由于解析出的 C#/VB 的输出是 CodeDom,因此输出可以由任何 CodeDomProvider 生成。所以它可以作为一个有效的语言转换器。
关注点
您只能转换可编译的代码。它不太擅长说明为什么没有编译。我发现的另一个问题是,它可能会解析文件,但生成的 CodeDom 是不完整的。这可能会导致 CodeDomProvider CodeGenerator 出现问题。通常会显示一些模糊的错误,例如 e 值为空。
CodeDomCodeProvider 的输出很丑陋,但它有助于您使用 CodeDom 构建代码生成器。我们的很多代码生成是通过数据库的元数据完成的。所以这段代码只会是一个起点。