Лучший способ генерировать код на С#, похожий на использование декораторов?

Скажем, у меня есть образец, который я постоянно повторяю. Что-то вроде:

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

Единственное отличие во всех этих случаях - это имя встроенной функции и метода обёртки. Есть ли способ генерировать их через что-то вроде декораторов? Сначала я думал, что атрибуты С# были декораторами. То есть, я мог бы сгенерировать код через что-то вроде [GenerateScaffolding("MyNativeCall1")]. Но, похоже, атрибуты скорее напоминают аннотации, создавая экземпляр класса, содержащего некоторые метаданные.

Также нет макросов С#. Так есть ли способ сделать это?

Несколько вещей, которые нужно иметь в виду:

  • Идея заключается в том, что методы-обертки имеют дополнительный код; они не просто называют нативные функции.
  • Идея также заключается в том, что сгенерированный код может чередоваться с другим существующим кодом внутри класса, а не для создания самого файла класса; что-то вроде декораторов или макросов C/С++.
  • Подход не должен зависеть от какой-либо конкретной среды разработки. В частности, я не на Visual Studio.

Ответ 1

Думаю, я отвечу на свое, с грустным: NOPE. Нет способа сделать это на С#. То есть, в самом языке и в каркасах нет ничего.

Если, однако, на Visual Studio есть шаблоны, например, RGraham и Pradeep; другие IDE могут иметь разные возможности/функции для этого. Но опять же, ничего похожего на препроцессор или декораторы в самом С#.

Ответ 2

Взяв идею из этой статьи MSDN на шаблонах T4, вы могли бы что-то вроде:

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

Ответ 3

Для этой цели существуют только фрагменты кода в Visual Studio.

Посмотрите на эту статью MSDN, в которой рассказывается, как создавать собственные пользовательские фрагменты кода. http://msdn.microsoft.com/en-us/library/ms165394.aspx

EDIT:

ОК.. Я вижу, что вы отредактировали свой вопрос и добавили, что вы не ищете специфичную для IDE функцию. Так что теперь мой ответ становится неуместным. Тем не менее, это может быть полезно для тех, кто приходит к поиску этой проблемы и ищет встроенную функцию Visual Studio.

Ответ 4

Вам не нужна среда IDE для генерации и обработки шаблонов во время выполнения, но вы должны создать свой собственный процессор директив и/или хост.

Engine engine = new Engine();

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

//transform the text template
string output = engine.ProcessTemplate(input, host);

В шаблоне вы можете смешивать язык шаблонов с кодом С# (генерация HTML-кода):

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

Вот как я использую T4 для генерировать все типы состояний машин из текстовых файлов.

Вы даже можете генерировать исходный код класса С# во время выполнения, компилировать и загружать и выполнять из своей программы.

Если вы объедините все эти методы, возможно даже с составными частями, например MEF, я уверен, что вы смогут достичь того, что вам нужно.

UPDATE без 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);

Наслаждайтесь и любезно отмечайте свой предпочтительный ответ.

Ответ 5

Вот еще один вариант, с небольшим количеством пыли в пикселях 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
}

Этот пример в основном показывает, как метод в одном классе может быть динамически переопределен методом из другого класса с тем же именем. Оригинальный Stub в C никогда не будет вызван. Вы должны будете соблюдать некоторые соглашения об именах.

Если вы объедините его с динамически связанными встроенными методами DLL, вы получите леса, для которых вам понадобится IMHO.