我有一个C#regex-parser程序,里面有三个文件,每个文件都包含一个静态类:
1)一个填充字符串字典的静态类
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)将这些值编译为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)基于这些字符串运行正则表达式的一些代码:
public static class Parser
{
// heavily simplified example
public static GroupCollection SearchStringForStates(string str)
{
return Regex.Match(str,
"^" + Patterns.StateUS,
RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase).Groups;
}
}
我希望能够像T4模板那样生成2),因为所有这些连接在每次执行时都是相同的:
@"\b(?<STATE><#=CharTree.GenerateRegex(Enumerable.Union(
AddrVals.USstates.Keys,
AddrVals.USstates.Values)#>)\b";
这样可行,但是如果我创建MyStringDicts
的新成员,或者添加/删除其词典中的某些值,T4模板将无法识别它们,直到从编译中排除Patterns.cs并重新编译。由于Parser
取决于Patterns
,这实际上不是一个选项 - 我需要T4转换来考虑对同一版本中其他文件的更改。
我不想做的事情:
MyStringDicts
拆分为自己的项目。我想将文件保存在一个项目中,因为它们是一个逻辑单元。 MyStringDicts
移到Patterns.cs的顶部即可。我也需要将MyStringDicts成员用于其他目的(例如,对于字典查找或其他T4模板。)我采用了关于使用T4Toolbox的VolatileAssembly
之类的建议here,但这似乎只适用于反向,因为在编辑T4模板后需要重新编译类文件。
我想要什么?
为了清晰起见而编辑
答案 0 :(得分:4)
我刚创建了一个小测试模板,它使用EnvDte(Visual Studio Automation)和T4Toolbox来运行第一个文件。它通过项目获取文件,因此在运行模板之前无需编译。事实上,它甚至可以获得未保存的更改......
这与FullSnabel使用的方法基本相同,但不需要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);
}
}
}
}
}
#>
如果您想坚持原始方法,这应该有用。
您似乎正在做的是将数据存储在类文件中。您可以考虑将代码存储在代码之外(在xml或ini文件中),并根据该数据生成这两个文件。这样你可以一起避免这个问题,它也可以使管理列表更容易。 如果您对列表的更改不太在意,您也可以将字典放在T4模板中。
另一种选择可能在代码中完全处理它。您可以创建一个Dictionary的子类,它具有'Pattern'属性(或GetPattern()函数)。然后解析器将使用AddrVals.USstates.Pattern,并且将不再需要patterns类。这样您就不需要任何代码生成了。
也许实际字典的包装器会更好,因为它允许您隐藏实际的集合以确保它在运行时不会被更改。有关示例,请参阅Is there a read-only generic dictionary available in .NET?。
答案 1 :(得分:3)
看看roslyn。它允许您将源文件编译为语法树,然后您可以检查和生成代码。这是一个CTP,但对我来说效果很好。
(添加了Roslyn样本)。
我在我的解决方案中创建了一个名为class2.cs的文件:
namespace StackOverflow
{
class Class2
{
public static int One() { return 8; }
public static int Eight(int x, double z) { return 8; }
}
}
使用Roslyn CTP(你还需要Visual studio SDK)我创建了这个简单的T4模板,该模板使用Roslyn来解析Class2.cs并根据它生成输出:
<#@ 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()");
<#
}
#>
}
}
}
此模板基于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()");
}
}
}
希望这有帮助