我已经开始涉足T4,最初相处得很好,但后来遇到了一个问题,实际上很明显,可能无法解决,但也许有一种方式,我只是缺乏经验,知道或看到。
给定以下类:
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; }
}
我想要这样的输出:
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); }
}
}
我已经弄清楚了我需要的反射的东西,我知道T4代码应该看起来像什么,但有一个问题:我需要它在同一个项目作为T4Test
类。然而,要加载程序集并对其进行反射,就需要对其进行编译——当然,如果我打算修改同一程序集的代码,这就有点困难了。(我猜NCrunch并没有简化事情。)
现在,我希望这些事情仍然有可能解决这个问题:
- 项目将在不生成类的情况下编译。这是因为类将实现由IoC容器自动注册/解析的接口。它也是不可测试的,因为
ActivityContext
不能被模拟。 - 因此,它不必一直在那里或正确。我只需要在实际交付DLL之前说"现在生成这个"。
- 出于同样的原因,我也不在乎T4模板是否实际上位于项目中-只要生成的文件最终在项目中(尽管不需要另一个项目的模板和构建PostBuild事件来复制
.cs
文件)。 - 准确地说,它甚至不需要是T4。如果有其他可行的方法,我也很乐意使用。
有办法做到这一点吗?
我想提出一种替代方法来反映生成的程序集,因为转换T4仅在项目成功构建并在程序集未过时时生成适当的输出时有效。
如果您使用特定于主机的T4模板,则可以通过EnvDTE接口访问Visual Studio自动化模型。使用它,您可以遍历当前加载的Visual Studio解决方案的CodeModel,而无需先构建它。
看看我对这个问题的回答:设计时反射。使用来自有形模板库的免费模板,您可以在设计时轻松地"反映"您现有的类,并检测用所需属性装饰的属性:
<#
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"))
{
...
}
}
}
#>
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("}");
}
#>