2012-06-27 25 views
6

tôi có một chương trình C# regex-phân tích cú pháp với ba tác phẩm trong đó, mỗi dòng chứa một lớp tĩnh:Tạo tập tin T4 khi file mã cùng lớp đã được sửa đổi

1) một lớp tĩnh đầy tự điển chuỗi

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) Một lớp học mà biên dịch các giá trị vào 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) một số mã chạy biểu thức thông thường dựa trên những chuỗi:

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

Tôi muốn để có thể tạo ra 2) như với một mẫu T4, như tất cả các nối này là giống hệt nhau trên tất cả các thực hiện:

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

này hoạt động, nhưng nếu tôi tạo ra một thành viên mới của MyStringDicts hoặc thêm/xóa một số giá trị khỏi từ điển của nó, mẫu T4 sẽ không nhận ra chúng cho đến khi loại trừ Patterns.cs khỏi biên dịch và biên dịch lại. Như Parser phụ thuộc vào Patterns, điều này thực sự không phải là một lựa chọn - Tôi cần chuyển đổi T4 để đưa vào tài khoản thay đổi các tập tin khác trong cùng một xây dựng.

Những điều tôi không muốn làm làm:

  • Chia MyStringDicts vào dự án riêng của mình. Tôi muốn giữ các tập tin trong một dự án, vì chúng là một đơn vị logic.
  • Chỉ cần di chuyển MyStringDicts vào đầu Patterns.cs. Tôi cần các thành viên MyStringDicts cho các mục đích khác nữa (để tra cứu từ điển, hoặc trong các mẫu T4 khác, ví dụ.)

tôi thông qua những lời khuyên here về việc sử dụng T4Toolbox của VolatileAssembly và như vậy, nhưng điều đó dường như chỉ làm việc cho hướng ngược lại, khi các tệp lớp cần được biên dịch lại sau khi chỉnh sửa mẫu T4.

Có gì Tôi muốn có thể?

sửa cho rõ ràng

+1

Bạn có thể giải thích thêm một số lý do tại sao bạn làm điều này không? Tôi có thể thấy một số cách để đối phó với điều này, nhưng nó là loại khó để biết cái nào là phù hợp trong kịch bản của bạn mà không có một số nền tảng. – AVee

+0

Btw, đây là những ý tưởng tôi đã có. Có lẽ gợi ý là đủ để giúp bạn đi. Các giải pháp được cung cấp bởi FuleSnabel có thể sẽ làm việc, bạn cũng có thể làm một cái gì đó tương tự bằng cách sử dụng những thứ trong không gian tên EnvDte. Nhưng có lẽ một cái gì đó đơn giản hơn sẽ làm. Bạn có thể xem xét việc đưa các lớp bạn cần vào T4 trong một dự án riêng biệt và chỉ tham chiếu nó từ các khuôn mẫu. Bạn cũng có thể tự động biên dịch và thực thi mã mà bạn cần động trong các mẫu T4 của mình. – AVee

+0

@AVee cảm ơn vì đã buộc tôi phải làm điều đúng và bao gồm trường hợp thực tế của tôi. – Arithmomaniac

Trả lời

4

tôi vừa tạo ra một mẫu thử nghiệm nhỏ trong đó sử dụng EnvDte (Visual Studio Automation) và T4Toolbox để chạy thông qua các tập tin đầu tiên. Nó chọn lên các tập tin thông qua dự án, do đó, không cần phải biên dịch trước khi chạy mẫu. Trong thực tế, nó thậm chí còn chọn những thay đổi chưa được lưu ...

Về cơ bản đây là phương pháp tương tự như FullSnabel sử dụng, nhưng không cần 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); 
        } 
       } 
      } 
     } 
    } 
#> 

Điều này sẽ hoạt động nếu bạn muốn tuân theo cách tiếp cận ban đầu của mình.

Điều bạn có vẻ đang làm là lưu trữ dữ liệu trong tệp lớp. Bạn có thể xem xét việc lưu trữ danh sách bên ngoài mã (trong tệp xml hoặc ini) và tạo cả hai tệp dựa trên dữ liệu đó. Bằng cách đó bạn tránh được vấn đề với nhau, nó cũng có thể giúp quản lý danh sách dễ dàng hơn. Nếu bạn không quan tâm quá nhiều về những thay đổi trong danh sách, bạn cũng có thể đặt các bộ từ điển bên trong mẫu T4.

Một giải pháp thay thế khác có thể xử lý hoàn toàn mã. Bạn có thể tạo một lớp con của Dictionary có thuộc tính 'Pattern' (hoặc hàm GetPattern()). Trình phân tích cú pháp sau đó sẽ sử dụng AddrVals.USstates.Pattern và lớp mẫu sẽ không cần nữa. Bằng cách này bạn sẽ không cần bất kỳ thế hệ mã nào.

Có lẽ trình bao bọc quanh từ điển thực sẽ tốt hơn vì nó cho phép bạn ẩn bộ sưu tập thực để đảm bảo nó không bị thay đổi khi chạy. Xem Is there a read-only generic dictionary available in .NET? để biết ví dụ về điều đó.

+0

Tôi thứ hai khái niệm rằng dữ liệu nên được lưu trữ trong một tập tin "nội dung" chứ không phải là mã. Làm cho toàn bộ shebang dễ đọc hơn và dễ bảo trì hơn. –

3

Hãy xem roslyn. Nó cho phép bạn biên dịch các tệp nguồn thành các cây cú pháp mà sau đó bạn có thể kiểm tra và tạo mã từ đó. Đó là một CTP nhưng nó làm việc khá tốt cho tôi.

(Đã thêm mẫu Roslyn).

tôi đã tạo ra một tập tin gọi là class2.cs trong dung dịch của tôi:

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

Sử dụng Roslyn CTP (bạn cần Visual studio SDK cũng) Tôi tạo ra này mẫu T4 đơn giản trong đó sử dụng để phân tích Roslyn Class2.cs và sản xuất ra dựa trên rằng:

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

mẫu này tạo ra đầu ra sau đây dựa trên 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

+0

Tôi thực sự thích một giải pháp T4, nhưng tôi sẽ lấy những gì tôi có thể nhận được. Bạn có một liên kết đến một ví dụ làm việc này? Nếu không có một, đó sẽ là waaay ra khỏi giải đấu của tôi. – Arithmomaniac

+0

Lưu ý; bạn sẽ sử dụng Roslyn để biên dịch mã C# thành cây cú pháp. Sau đó, bạn sẽ sử dụng T4 để tạo mã. Thật không may tôi không có một mẫu làm việc ngay bây giờ. – FuleSnabel

+3

Tôi đã thêm một mẫu để minh họa cách sử dụng Roslyn từ T4. – FuleSnabel

Các vấn đề liên quan