Best way to generate code in C# similar to using decorators?

Taking the idea from this MSDN article on T4 Templates, you could so something like:

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

You don't need the IDE to generate and process templates at runtime, but you have to create your own directive processor and/or host.

Engine engine = new Engine();

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

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

In your template, you can mix template language with C# code (sample HTML generation):

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

Here is how I use T4 to generate all kinds of state machines from text files.

You can even generate source code for C# class at runtime, compile and load and execute from your program.

If you combine all those techniques, perhaps even with composable parts, like MEF, I'm sure you will be able to achieve what you need.

UPDATE without MEF, but you still need IDE to pre-process the template.

Since I don't have your DLL, I can't give you an exact answer, but perhaps this will help.

Given this template (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);
       }
    <# } #>
}

and this Program:

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

}

it will print 2, since it is a square root of 4.

UPDATE 2

After slightly modifying the CustomCmdLineHost from the second link above:

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

the sample program no longer requires the 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

I hope this satisfies your needs.

UPDATE 3

If you further modify the sample host like this:

internal string TemplateFileValue = Path.Combine(
    AppDomain.CurrentDomain.BaseDirectory,"CustomCmdLineHost.tt");

Then you can avoid having to specify the template file name and just use in-memory processing:

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

Enjoy and kindly mark your preferred answer.


Code snippets in Visual Studio exist for this purpose only.

Have a look at this MSDN article that teaches you how to create your own custom code snippets. http://msdn.microsoft.com/en-us/library/ms165394.aspx

  EDIT:

OK.. I see that you edited your question and added that you are not looking for an IDE specific feature. So my reply becomes irrelevent now. Nevertheless, it may be useful for someone who comes searching for this problem and is looking for a built in Visual Studio feature.

Tags:

C#