Using extension methods in T4 templates


Adam has a groovy post on T4, but he’s under the impression that you can’t use C# extension methods in T4. I just wanted to discuss the limitations that exist and show how you can take advantage of this cool C#/VB 3.5 feature.

Extension methods require you to declare a top-level static class, and don’t support nested class scope.

With T4, it’s necessary to create a class feature block <#+ #> if you want to declare another class directly in your template and class feature blocks translate this into a nested class.

This means you can’t use the really obvious syntax of

   1: <#@ template language="C#v3.5" debug="true" #>
   2: <#@ import namespace="System.Collections.Generic" #>
   3: <#
   4: List<string> strings = new List<string>();
   5: strings.Add("Foo");
   6: strings.Add("Bar");
   7: foreach ( string item in strings)
   8: {
   9: #>
  10: This is a test of <#= item.Wooga() #>
  11: <#
  12: }
  13: #>
  14:  
  15: <#+
  16:     public static class WoogaHelper
  17:     {
  18:         public static string Wooga(this string input)
  19:         {
  20:             return "Wooga" + input;
  21:         }
  22:     }
  23: #>
  24:  
  25:  

BTW, note that I’ve set language=”C#v3.5″ in my template directive to switch T4 to use the latest compiler.

However, to get things to work, we need to move our WoogaHelper class into a separate assembly and import that into T4; then all is sunny and happy in the world.

In a library assembly building to produce ExtMethods.dll:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace Extmethods
   7: {
   8:     public static class WoogaHelper
   9:     {
  10:         public static string Wooga(this string input)
  11:         {
  12:             return "Wooga" + input;
  13:         }
  14:     }
  15: }

Then add a reference to the ExtMethods assembly (either as an assembly or project reference) to the project that contains your T4 template and add an assembly and import directive to your template, like so:

   1: <#@ template language="C#v3.5" debug="true" #>
   2: <#@ import namespace="System.Collections.Generic" #>
   3: <#@ import namespace="Extmethods" #>
   4: <#@ assembly name="Extmethods.dll" #>
   5: <#
   6: List<string> strings = new List<string>();
   7: strings.Add("Foo");
   8: strings.Add("Bar");
   9: foreach ( string item in strings)
  10: {
  11: #>
  12: This is a test of <#= item.Wooga() #>
  13: <#
  14: }
  15: #>
  16:  

 

Extension methods can be particularly helpful within T4, as you often want to do some obscure code spit based on a type that would make no sense given an item of that type in any other context. Enjoy.

 

Technorati Tags: ,,
Comments (4)

  1. I’d use that approach even if T4 supported nested classes. I believe T4 scripts should be as concise as possible, for obvious reasons (readability, debugging, maintanability, etc.).

    For that to happen, we should have, from one side, well-defined and simple to use framework completion interfaces. From the other side, powerful domain APIs are also welcome.

    BR,

    — AFurtado

  2. Sajjad says:

    right now T4 has good number of operators like

    <#=  (expression level)

    <#   (statement level)

    <#+  (class level)

    we need another one

    <#* (namespace level)

    another improvement i wish they do is that if i use

    <#+

    there should be a better way to do templating same as i can do at file start level may be somthing like

    <#+ void GenerateACode(int x) { &>

    this is my template

    <# if(x==1){ #>

       second part

    <# } #

    #>

    with &> it would produce output within functions scope of <#+

    etc

    Thanks,

    Sajjad

  3. Gareth Jones says:

    The namespace-level operator is an interesting idea, although it would make combining templates more tricky.

    I’m a bit lost with your second request.

    it look sliek you’re asking to be able to mix function declarations with boilerplate blocks?

    If so, then this already works, but you need to use <#+ for all new blocks AFTER the first <#+ section.