Create a T4 file when code files of the same class have been modified

I have a C # regex program with three files in it, each of which contains a static class:

1) one static class filled with string dictionaries

static class MyStringDicts { internal static readonly Dictionary<string, string> USstates = new Dictionary<string, string>() { { "ALABAMA", "AL" }, { "ALASKA", "AK" }, { "AMERICAN SAMOA", "AS" }, { "ARIZONA", "AZ" }, { "ARKANSAS", "AR" } // and so on } // and some other dictionaries } 

2) The class that compiles these values ​​in Regex

 public static class Patterns { Public static readonly string StateUS = @"\b(?<STATE>" + CharTree.GenerateRegex(Enumerable.Union( AddrVals.USstates.Keys, AddrVals.USstates.Values)) + @")\b"; //and some more like these } 

3) some code that runs regular expressions based on these lines:

 public static class Parser { // heavily simplified example public static GroupCollection SearchStringForStates(string str) { return Regex.Match(str, "^" + Patterns.StateUS, RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase).Groups; } } 

I would like to be able to generate 2) both with the T4 template, since all this concatenation is the same with every execution:

 @"\b(?<STATE><#=CharTree.GenerateRegex(Enumerable.Union( AddrVals.USstates.Keys, AddrVals.USstates.Values)#>)\b"; 

This works, but if I create a new MyStringDicts member or add / remove some values ​​from its dictionaries, the T4 template will not recognize them until the exception of Patterns.cs from compilation and recompilation. Since Parser dependent on Patterns , this is really not an option. I need a T4 conversion to account for changes in other files in the same assembly.

Things I don't want to do:

  • Split MyStringDicts into your own project. I would like to save the files in one project, since they are a logical unit.
  • Just move MyStringDicts to the beginning of Patterns.cs. I also need MyStringDicts members for other purposes (for example, to search in a dictionary or in other T4 templates).

I took advice here about using T4Toolbox VolatileAssembly and the like, but it seems to only work in the opposite direction when class files need to be recompiled after editing the T4 template.

I want this to be possible?

edited for clarity

+6
source share
2 answers

I just created a small test pattern that uses EnvDte (Visual Studio Automation) and T4Toolbox to run through the first file. It takes the file through the project, so there is no need to compile it before running the template. In fact, it even picks up unsaved changes ...

This is basically the same approach as FullSnabel, but without the need for Roslyn.

 <#@ template debug="false" hostspecific="True" language="C#" #> <#@ output extension=".cs" #> <#@ Assembly Name="System.Core.dll" #> <#@ dte processor="T4Toolbox.DteProcessor" #> <#@ TransformationContext processor="T4Toolbox.TransformationContextProcessor" #> <#@ assembly name="System.Xml" #> <#@ assembly name="EnvDTE" #> <#@ assembly name="EnvDTE80" #> <#@ import namespace="T4Toolbox" #> <#@ import namespace="EnvDTE" #> <#@ import namespace="EnvDTE80" #> <# ProjectItem projectItem = TransformationContext.FindProjectItem("Dictionaries.cs"); FileCodeModel codeModel = projectItem.FileCodeModel; foreach (CodeElement element in codeModel.CodeElements) { CodeNamespace ns = element as CodeNamespace; if(ns != null) { foreach(CodeElement ele in ns.Children) { CodeClass cl = ele as CodeClass; if(cl != null && cl.Name == "Dictionaries") { foreach(CodeElement member in cl.Members) { // Generate stuff... this.WriteLine(member.Name); } } } } } #> 

This should work if you want to stick with your original approach.

What you do is store data in a class file. You can consider storing your lists outside of the code (in an xml or ini file) and generate both files based on this data. This way you avoid the problem together, it can also facilitate list management. If you don’t care too much about the changes to the list, you can also put dictionaries inside the T4 template itself.

Another alternative may relate entirely to this code. You can create a subclass of the Dictionary that has the "Pattern" (or GetPattern ()) property. Then the analyzer will use AddrVals.USstates.Pattern, and the template class will no longer be needed. This way you do not need code generation.

Perhaps wrapping around the actual dictionary would be better because it allows you to hide the actual collection to make sure that it has not changed at run time. See, as an example of this, is there a public read dictionary available in .NET? .

+4
source

Take a look at roslyn . This allows you to compile the source files into syntax trees, which you can then check and generate the code. This is CTP, but for me it worked pretty well.

(Roslyn sample added).

I created a file called class2.cs in my solution:

 namespace StackOverflow { class Class2 { public static int One() { return 8; } public static int Eight(int x, double z) { return 8; } } } 

Using Roslyn CTP (you need the Visual Studio SDK ), I created this simple T4 template that uses Roslyn to parse Class2.cs and produce output based on this:

 <#@ template hostspecific= "true" #> <#@ assembly name = "System.Core" #> <#@ assembly name = "Roslyn.Compilers" #> <#@ assembly name = "Roslyn.Compilers.CSharp" #> <#@ import namespace = "System.IO" #> <#@ import namespace = "System.Linq" #> <#@ import namespace = "Roslyn.Compilers.CSharp" #> <# var host = Path.GetFullPath(Host.ResolvePath(@".\Class2.cs")); var content = File.ReadAllText(host); var tree = SyntaxTree.ParseCompilationUnit(content); var methods = tree .GetRoot() .ChildNodes() .OfType<NamespaceDeclarationSyntax>() .SelectMany(x => x.ChildNodes()) .OfType<ClassDeclarationSyntax>() .SelectMany(x => x.ChildNodes()) .OfType<MethodDeclarationSyntax>() .ToArray() ; #> namespace StackOverflow { using System; static partial class Program { public static void Main() { <# foreach (var method in methods) { var parent = (ClassDeclarationSyntax)method.Parent; var types = method .ParameterList .ChildNodes() .OfType<ParameterSyntax>() .Select(t => t.Type.PlainName) .ToArray() ; var plist = string.Join(", ", types); #> Console.WriteLine("<#=parent.Identifier.ValueText#>.<#=method.Identifier.ValueText#>(<#=plist#>).ToString()"); <# } #> } } } 

This template creates the following result based on Class2.cs:

 namespace StackOverflow { using System; static partial class Program { public static void Main() { Console.WriteLine("Class2.One().ToString()"); Console.WriteLine("Class2.Eight(int, double).ToString()"); } } } 

Hope this helps

+3
source

Source: https://habr.com/ru/post/919159/


All Articles