Generate code via Reflection over the same assembly

I started working in T4 and got along well at first, but then I came across a problem that is actually quite obvious and may not be solvable, but maybe there is a way that I just do not have enough experience to know or see.

Given the following class:

public class T4Test : CodeActivity { protected override void Execute(CodeActivityContext context) { } [Input("InX")] public InArgument<string> InX { get; set; } [Output("OutX")] public OutArgument<string> OutX { get; set; } } 

I want this as a conclusion:

 public class ActivityWrapper { private readonly T4Test _activity; private readonly ActivityContext _context; public ActivityWrapper(T4Test activity, ActivityContext context) { this._activity = activity; this._context = context; } public string InX { get { return this._activity.InX.Get(this._context); } } public string OutX { get { return this._activity.OutX.Get(this._context); } set { this._activity.OutX.Set(this._context, value); } } } 

I calculated the Reflection material that I need, and I know how the T4 code looks, but there is one problem: I need it in the same project as the T4Test class. However, in order to load the assembly and reflect it, it needs to be compiled, but of course it is a bit complicated if I intend to change the same assembly code. (And I think NCrunch is not simplifying things.)

Now all I hope can still solve this:

  • The project will be compiled without a generated class. This is because the class will implement interfaces that will be automatically registered / enabled by the IoC container. In any case, this is also impossible to verify, because the ActivityContext cannot be mocked.
  • For this reason, he should not be there or correct all the time. I just need to say “generate this now” before delivering the DLL.
  • For the same reason, I also don’t care if the T4 template is actually in the project - as long as the generated file gets into the project (although without the need for another project for the template and creating PostBuild events for copying .cs ).
  • To be precise, you don’t even have to be T4. If there are other possible ways to do this, I will be happy to use this as well.

Is there any way to achieve this? (And was it clear enough?)

+4
source share
2 answers

T4Test.tt

 <#@ include file="Activities.tt" #> <# var t4test = new Activity("T4Test") { Input("InX"), Output("OutX"), }; GenerateCode(t4test); #> 

Activities.tt

 <#@ template language="C#" #> <#@ import namespace="System.Collections" #> <#@ import namespace="System.Collections.Generic" #> <#+ class Activity : IEnumerable<Property> { private string name, wrapper; private List<Property> properties; public Activity(string name, string wrapper = null) { this.name = name; this.wrapper = wrapper ?? name + "Wrapper"; this.properties = new List<Property>(); } public void Add(Property property) { this.properties.Add(property); } public IEnumerator<Property> GetEnumerator() { return this.properties.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void GenerateCode() { // ... } } class Property { private bool output; private string name, type; public Property(bool output, string name, string type) { this.output = output; this.name = name; this.type = type; } } Property Input(string name, string type = "string") { return new Property(false, name, type); } Property Output(string name, string type = "string") { return new Property(true, name, type); } void GenerateCode(params Activity[] activities) { WriteLine("namespace Foo"); WriteLine("{"); PushIndent(" "); foreach (var activity in activities) { WriteLine("class " + activity.name); WriteLine("{"); PushIndent(" "); // ... PopIndent(); WriteLine("}"); } PopIndent(); WriteLine("}"); } #> 
+3
source

I would like to offer an alternative to reflecting the generated assembly, since the T4 conversion only works when the project is successfully built and generates the correct output if the assembly is not outdated.

If you use a T4 template with a specific hosting, you get access to the Visual Studio automation model through EnvDTE interfaces. Using this, you can go through the CodeModel of your currently loaded Visual Studio solution without having to create it first.

Take a look at my answer to this SO question: Reflection of design time . Using a free template from the tangible gallery of templates, you can easily “reflect” existing classes at design time and define properties decorated with the desired attributes:

 <# 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) { // iterate all properties var allProperties = VisualStudioHelper.GetAllCodeElementsOfType(codeClass.Members, EnvDTE.vsCMElement.vsCMElementProperty, true); foreach(EnvDTE.CodeProperty property in allProperties) { // check if it is decorated with an "Input"-Attribute if (property.Attributes.OfType<EnvDTE.CodeAttribute>().Any(a => a.FullName == "Input")) { ... } } } #> 
+6
source

All Articles