11

Is there a way to do reflection pre-compile - at design time?

My intent is to use T4 to spit out custom codes based on classes that implement certain interfaces. I know I can call upon reflection, but I want the T4 script to spit out the additional code before compile, otherwise I will need to compile the code twice, once to generate dlls, twice to let T4 reflect on the previously generated dll and add additional scaffolding.

Is there a way to do reflection at design time?

Is there a better way to do this?

Alwyn
  • 8,079
  • 12
  • 59
  • 107
  • Are the templates and the classes in the same project? I'm just guessing, but maybe you can have better control over the order if they were in different projects, so one can compile after the other. – Kobi Jan 03 '13 at 06:55
  • Yeah I thought about that too, but you still end up building your project one by one, unless there's a way to stop the build, run t4, unpause. – Alwyn Jan 03 '13 at 18:50
  • 1
    You don't have to stop the build if template generation is a part of it: [Code Generation in a Build Process](http://msdn.microsoft.com/en-us/library/ee847423.aspx). I never actually done that, and not sure in which context the templating engine runs, but it looks like it can work. – Kobi Jan 03 '13 at 21:30
  • @Kobi Looks promising will give it a shot. – Alwyn Jan 04 '13 at 05:43

3 Answers3

25

There actually is a way of generating code pre-build based on the CodeModel provided by Visual Studio Automation: The Project Interface provides a Property "CodeModel" that contains a graph of all model artifacts in that project. You might want to traverse it in order to find classes, interfaces, properties, ... based on which you generate your output code.

dandrejw already mentioned the Tangible T4-Editor. It has got a free template gallery. There is a reusable template "tangible Visual Studio Automation Helper" which should be extremely helpful in your case. Using this template you could solve your issue like this:

This is code within a t4 template detecting all classes that implement INotifyPropertyChanged.

<#
    // get a reference to the project of this t4 template
    var project = VisualStudioHelper.CurrentProject;
    // get all class items from the code model
    var allClasses = VisualStudioHelper.GetAllCodeElementsOfType(project.CodeModel.CodeElements, EnvDTE.vsCMElement.vsCMElementClass, false);

    // iterate all classes
    foreach(EnvDTE.CodeClass codeClass in allClasses)
    {
        // get all interfaces implemented by this class
        var allInterfaces = VisualStudioHelper.GetAllCodeElementsOfType(codeClass.ImplementedInterfaces, EnvDTE.vsCMElement.vsCMElementInterface, true);
        if (allInterfaces.OfType<EnvDTE.CodeInterface>()
                         .Any(i => i.Name == "INotifyPropertyChanged"))
        {
            #>Render your code here<#
        }
    }
#>

Put your output code where the code snippet says "Render your code here".

Nico
  • 2,120
  • 16
  • 20
  • How does this behave without Visual Studio (i.e. on a build server, using MSBuild only)? – julealgon Dec 16 '13 at 18:38
  • I would not expect this code to work without Visual Studio. One can only access the EnvDTE CodeModel when Visual Studio is running and provides the host for the template. However, if you found another way to instantiate the logic for accessing EnvDTE without VS, one might get this code to work... I'm sorry. – Nico Dec 16 '13 at 21:05
9

For any future readers not in the mood to try and get the T4 VisualStudioHelper template working, below is a self-contained template which enumerates all the classes in the current project. It is tested in Visual Studio 2013 and was inspired by the code on the T4 Site

<#@ template  debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core" #>
<#@ assembly name="EnvDte" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #> 
<#    
  foreach(var ns in GetNamespaceElements())
  {
    foreach(var cc in ns.Members.OfType<EnvDTE.CodeClass>())
    {
#>Render your code here<#
    }
  }
#>

<#+
  public IEnumerable<EnvDTE.CodeNamespace> GetNamespaceElements()
  {
    var visualStudio = (this.Host as IServiceProvider).GetService(typeof(EnvDTE.DTE))
                        as EnvDTE.DTE;
    var project = visualStudio.Solution.FindProjectItem(this.Host.TemplateFile)
                  .ContainingProject as EnvDTE.Project;

    var projItems = new List<EnvDTE.ProjectItem>();
    FillProjectItems(project.ProjectItems, projItems);
    var names = new HashSet<string>(projItems
      .Where(i => i.FileCodeModel != null)
      .SelectMany(i => i.FileCodeModel.CodeElements.OfType<EnvDTE.CodeElement>())
      .Where(e => e.Kind == EnvDTE.vsCMElement.vsCMElementNamespace)
      .Select(e => e.FullName));

    var codeNs = new List<EnvDTE.CodeNamespace>();
    FillCodeNamespaces(project.CodeModel.CodeElements.OfType<EnvDTE.CodeNamespace>(), codeNs);

    return codeNs.Where(ns => names.Contains(ns.FullName));
  }

  public void FillCodeNamespaces(IEnumerable<EnvDTE.CodeNamespace> parents, List<EnvDTE.CodeNamespace> all)
  {
    foreach (var parent in parents)
    {
      all.Add(parent);
      FillCodeNamespaces(parent.Members.OfType<EnvDTE.CodeNamespace>(), all);
    }
  }

  public void FillProjectItems(EnvDTE.ProjectItems items, List<EnvDTE.ProjectItem> ret)
  {
    if (items == null) return;
    foreach(EnvDTE.ProjectItem item in items)
    {
      ret.Add(item);
      FillProjectItems(item.ProjectItems, ret);
    }
  }
#>
erdomke
  • 4,980
  • 1
  • 24
  • 30
0

The only way I know of to do this is to make use of some code parsing capability. I can't think of a way off the top of my head how to do that. I'm sure .NET has some utilities that can do that.

I'm not sure what your situation is, but usually instead of reading code, you have some centralized piece of information, be it an XML file or some UML diagram (class diagram even) that is used to generate code from. It simplifies things a bit and makes it easier to make changes and let code generation spit out the changes. Have a look at the Tangible T4 tools for Visual Studio.

Dandré
  • 2,053
  • 3
  • 18
  • 38