65.9K
CodeProject 正在变化。 阅读更多。
Home

CodeDom 助手

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (25投票s)

2007年9月21日

4分钟阅读

viewsIcon

153421

downloadIcon

6666

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

Screenshot - codedomassistant.gif

引言

我觉得编写 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; )
{
}

continuebreak 语句是通过 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 构建代码生成器。我们的很多代码生成是通过数据库的元数据完成的。所以这段代码只会是一个起点。

© . All rights reserved.