说我有一种我不断重复的模式.就像是:
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);
  }
}
所有这些中唯一不同的是本机函数和包装器方法的名称.有没有办法通过类似装饰器的东西生成它们?起初我认为C#属性是装饰器.也就是说,我可以通过类似的方式生成代码[GenerateScaffolding("MyNativeCall1")].但似乎属性更像是注释,实例化一个包含一些元数据的类.
C#也没有宏.有没有办法做到这一点?
要记住以下几点:
<#@ 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);
    }
<# }  #>
}
您不需要 IDE 在运行时生成和处理模板,但您必须创建自己的指令处理器和/或主机。
Engine engine = new Engine();
//read the text template
string input = File.ReadAllText(templateFileName);
//transform the text template
string output = engine.ProcessTemplate(input, host);
在您的模板中,您可以将模板语言与 C# 代码混合使用(示例 HTML 生成):
<table>
  <# for (int i = 1; i <= 10; i++)
     { #>
        <tr><td>Test name <#= i #> </td>
          <td>Test value <#= i * i #> </td> 
        </tr>
  <# } #>
</table>
下面是我如何使用 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);
       }
    <# } #>
}
和这个程序:
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})); 
        }
    }
}
它将打印 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
            };
        }
    }
示例程序不再需要 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
我希望这能满足您的需求。
更新 3
如果您像这样进一步修改示例主机:
internal string TemplateFileValue = Path.Combine(
    AppDomain.CurrentDomain.BaseDirectory,"CustomCmdLineHost.tt");
然后你就可以避免指定模板文件名而只使用内存处理:
var host = new CustomCmdLineHost();
Engine engine = new Engine();
string input = File.ReadAllText("ExtDll.tt");
string source = engine.ProcessTemplate(input, host);
享受并亲切地标记您的首选答案。
我想我会悲伤地回答自己:不。在 C# 中没有办法做到这一点。也就是说,语言本身和框架中都没有任何内容。
然而,如果使用 Visual Studio,就会有模板,就像 RGraham 和 Pradeep 指出的那样;其他 IDE 可能也有不同的设施/功能来完成此任务。但同样,C# 本身没有什么比预处理器或装饰器更好的了。