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

时间:2014-06-25 07:26:50

标签: 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);
  }
}

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

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

要记住的一些事项:

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

6 个答案:

答案 0 :(得分:9)

从T4模板上的MSDN article中获取想法,你可以这样:

<#@ 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);
    }
<# }  #>
}

答案 1 :(得分:4)

Visual Studio中的代码片段仅用于此目的。

查看此MSDN文章,该文章教您如何创建自己的自定义代码段。 http://msdn.microsoft.com/en-us/library/ms165394.aspx

修改

好的..我看到你编辑了你的问题,并补充说你不是在寻找一个特定于IDE的功能。所以我的回复现在变得无关紧要了。尽管如此,对于寻找此问题并正在寻找内置Visual Studio功能的人来说,它可能会有用。

答案 2 :(得分:4)

您不需要IDE在运行时生成和处理模板,但您必须create your own directive processor和/或host

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到generate all kinds of state machines

您甚至可以为C# class at runtime, compile and load生成源代码并从您的程序执行。

如果你将所有这些技巧结合起来,甚至可能使用可组合的部分,例如MEF,我相信你将能够实现你所需要的。

没有MEF的

更新 ,但您仍需要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);

尽情并标记您的首选答案。

答案 3 :(得分:1)

我想我会回答我自己的一个悲伤:NOPE。在C#中无法做到这一点。也就是说,语言本身和框架都没有。

然而,如果有一个是在Visual Studio上,那么有模板,比如RGraham和Pradeep指出的;其他IDE可能具有不同的功能/特性来完成此任务。但同样,C#本身并不像预处理器或装饰器。

答案 4 :(得分:0)

这是另一种选择,带有一点PostSharp的AOP小精灵尘埃:

using System;
using System.Reflection;

using PostSharp.Aspects;

internal class Program
{
    #region Methods

    private static void Main(string[] args)
    {
        Action action = () => { Console.WriteLine("Action called."); };
        Console.WriteLine(C.MyWrapper1(action));
    }

    #endregion
}

[Scaffolding(AttributeTargetMembers = "MyWrapper*")]
internal static class C
{
    #region Public Methods and Operators

    public static uint MyWrapper1(Action a)
    {
        DoSomething1();
        return Stub(a);
    }

    #endregion

    #region Methods

    private static void DoSomething1() { Console.WriteLine("DoSomething1"); }

    private static uint Stub(Action a) { return 0; }

    #endregion
}

internal static class ExternalStubClass
{
    #region Public Methods and Operators

    public static uint Stub(Action a)
    {
        a.Invoke();
        return 5;
    }

    #endregion
}

[Serializable]
public class ScaffoldingAttribute : OnMethodBoundaryAspect
{
    #region Fields

    private MethodInfo doSomethingInfo;

    #endregion

    #region Public Methods and Operators

    public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
    {
        Type type = typeof(C);
        this.doSomethingInfo = type.GetMethod(method.Name.Replace("MyWrapper", "DoSomething"), BindingFlags.NonPublic | BindingFlags.Static);
    }

    public override void OnEntry(MethodExecutionArgs args)
    {
        this.doSomethingInfo.Invoke(null, null);
        args.ReturnValue = ExternalStubClass.Stub(args.Arguments[0] as Action);
        args.FlowBehavior = FlowBehavior.Return;
    }

    #endregion
}

此示例基本上显示了一个类中的方法如何被另一个具有相同名称的类的方法动态覆盖。 C中的原始Stub永远不会被调用。您必须遵守一些命名约定。

如果你将它与Dynamically bound native DLL methods结合使用,你将获得需要恕我直言的脚手架。

答案 5 :(得分:0)

根据dotnet的最新更新,您可以使用source code egenerators