在C#中生成代码的最佳方法类似于使用装饰器?

Sal*_*rtz 16 c#

说我有一种我不断重复的模式.就像是:

static class C {
  [DllImport("mydll")]
  private static extern uint MyNativeCall1(Action a);
  public static uint MyWrapper1(Action a) {
    // Do something
    return MyNativeCall1(a);
  }

  [DllImport("mydll")]
  private static extern uint MyNativeCall2(Action a);
  public static uint MyWrapper2(Action a) {
    // Do something
    return MyNativeCall2(a);
  }

  //...

  [DllImport("mydll")]
  private static extern uint MyNativeCallN(Action a);
  public static uint MyWrapperN(Action a) {
    // Do something
    return MyNativeCallN(a);
  }
}
Run Code Online (Sandbox Code Playgroud)

所有这些中唯一不同的是本机函数和包装器方法的名称.有没有办法通过类似装饰器的东西生成它们?起初我认为C#属性是装饰器.也就是说,我可以通过类似的方式生成代码[GenerateScaffolding("MyNativeCall1")].但似乎属性更像是注释,实例化一个包含一些元数据的类.

C#也没有宏.有没有办法做到这一点?

要记住以下几点:

  • 我们的想法是包装器方法有额外的代码; 它们不仅仅是调用本机功能.
  • 这个想法也是生成的代码可以与类中的其他现有代码交错,而不是生成类文件本身; 类似装饰器或C/C++宏的东西.
  • 该方法不应该依赖于任何特定的IDE.具体来说,我不在Visual Studio上.

Cod*_*gue 9

这篇关于T4模板的MSDN文章中获取想法,你可以这样:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
static class C {
<#  
    int N = 15;
    for(int i=0; i<N; i++)
    { #>
    [DllImport("mydll")]
    private static extern uint MyNativeCall<#= i #>(Action a);
    public static uint MyWrapper<%#= i #>(Action a) {
        return MyNativeCall<#= i #>(a);
    }
<# }  #>
}
Run Code Online (Sandbox Code Playgroud)


Dar*_*rek 5

您不需要 IDE 在运行时生成和处理模板,但您必须创建自己的指令处理器和/或主机

Engine engine = new Engine();

//read the text template
string input = File.ReadAllText(templateFileName);

//transform the text template
string output = engine.ProcessTemplate(input, host);
Run Code Online (Sandbox Code Playgroud)

在您的模板中,您可以将模板语言与 C# 代码混合使用(示例 HTML 生成):

<table>
  <# for (int i = 1; i <= 10; i++)
     { #>
        <tr><td>Test name <#= i #> </td>
          <td>Test value <#= i * i #> </td> 
        </tr>
  <# } #>
</table>
Run Code Online (Sandbox Code Playgroud)

下面是我如何使用 T4从文本文件生成各种状态机

您甚至可以在运行时C# 类生成源代码,从您的程序编译、加载和执行。

如果您将所有这些技术结合起来,甚至可能与诸如MEF 之类的可组合部件结合使用,我相信您将能够实现您所需要的。

没有 MEF 的UPDATE ,但您仍然需要 IDE 来预处理模板。

因为我没有你的 DLL,所以我不能给你一个确切的答案,但也许这会有所帮助。

鉴于此模板(ExtDll.tt):

<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="mscorlib" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<# 
            var extraCodeArray = new[]
                             { string.Empty,
                                 "var localVar = 1;",
                                 "var localVar = 2;",
                                 "var localVar = 3;",
                                 "var localVar = 4;",
                                 "var localVar = 5;",
                                 "var localVar = 6;",
                                 "var localVar = 7;",
                                 "var localVar = 8;",
                                 "var localVar = 9;",
                                 "var localVar = 10;",
                             };

#>
using System;
static class C{
<# for (int i = 1; i <= 10; i++)
       { #>
       public static double MyWrapper<#= i #>(Func<int,double> a) {
         <#= extraCodeArray[i] #>
       return a.Invoke(localVar);
       }
    <# } #>
}
Run Code Online (Sandbox Code Playgroud)

和这个程序:

using System;
using System.Linq;

namespace ConsoleApplication4
{
    using System.CodeDom.Compiler;
    using System.Reflection;

    using Microsoft.CSharp;

    class Program
    {
        static void Main(string[] args)
        {
            ExtDll code = new ExtDll();
            string source = code.TransformText();
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters()
                                            {
                                                GenerateInMemory = true,
                                                GenerateExecutable = false
                                            };
            parameters.ReferencedAssemblies.AddRange(
                new[]
                {
                    "System.Core.dll",
                    "mscorlib.dll"
                });
            CompilerResults results = provider.CompileAssemblyFromSource(parameters, source);
            if (results.Errors.HasErrors)
            {
                var errorString = String.Join("\n", results.Errors.Cast<CompilerError>().Select(error => String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText)));

                throw new InvalidOperationException(errorString);
            }
            Assembly assembly = results.CompiledAssembly;
            Func<int,double> squareRoot = (i) => { return Math.Sqrt(i); };
            Type type = assembly.GetType("C");
            //object instance = Activator.CreateInstance(type);
            MethodInfo method = type.GetMethod("MyWrapper4");
            Console.WriteLine(method.Invoke(null, new object[]{squareRoot})); 
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

它将打印 2,因为它是 4 的平方根。

更新 2

从上面的第二个链接稍微修改 CustomCmdLineHost 后:

    public IList<string> StandardAssemblyReferences
    {
        get
        {
            return new string[]
            {
                //If this host searches standard paths and the GAC,
                //we can specify the assembly name like this.
                //---------------------------------------------------------
                //"System"

                //Because this host only resolves assemblies from the 
                //fully qualified path and name of the assembly,
                //this is a quick way to get the code to give us the
                //fully qualified path and name of the System assembly.
                //---------------------------------------------------------
                typeof(System.Uri).Assembly.Location,
                typeof(System.Linq.Enumerable).Assembly.Location
            };
        }
    }
Run Code Online (Sandbox Code Playgroud)

示例程序不再需要 IDE:

        var host = new CustomCmdLineHost();
        host.TemplateFileValue = "ExtDll.tt";
        Engine engine = new Engine();
        string input = File.ReadAllText("ExtDll.tt");
        string source = engine.ProcessTemplate(input, host);
        if (host.Errors.HasErrors)
        {
            var errorString = String.Join("\n", host.Errors.Cast<CompilerError>().Select(error => String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText)));

            throw new InvalidOperationException(errorString);            
        }

        CSharpCodeProvider provider = new CSharpCodeProvider();
... rest of the code as before
Run Code Online (Sandbox Code Playgroud)

我希望这能满足您的需求。

更新 3

如果您像这样进一步修改示例主机:

internal string TemplateFileValue = Path.Combine(
    AppDomain.CurrentDomain.BaseDirectory,"CustomCmdLineHost.tt");
Run Code Online (Sandbox Code Playgroud)

然后你就可以避免指定模板文件名而只使用内存处理:

var host = new CustomCmdLineHost();
Engine engine = new Engine();
string input = File.ReadAllText("ExtDll.tt");
string source = engine.ProcessTemplate(input, host);
Run Code Online (Sandbox Code Playgroud)

享受并亲切地标记您的首选答案。


Sal*_*rtz 1

我想我会悲伤地回答自己:不。在 C# 中没有办法做到这一点。也就是说,语言本身和框架中都没有任何内容。

然而,如果使用 Visual Studio,就会有模板,就像 RGraham 和 Pradeep 指出的那样;其他 IDE 可能也有不同的设施/功能来完成此任务。但同样,C# 本身没有什么比预处理器或装饰器更好的了。

  • 不仅是 Visual Studio,还包括任何支持构建前/构建后命令(因此能够执行 bash/shell)的 IDE。任何值得信赖的 IDE 都应该支持这些。 (2认同)