如果您真的希望能够在不必编写大量代码的情况下链接属性设置,那么一种方法是使用代码生成 (CodeDom)。您可以使用反射来获取可变属性的列表,生成一个流畅的构建器类,并使用最终Build()
方法返回您实际尝试创建的类。
我将跳过所有关于如何注册自定义工具的样板文件 - 这很容易找到文档,但仍然冗长,我认为我不会通过包含它来添加太多内容。不过,我将向您展示我对 codegen 的想法。
public static class PropertyBuilderGenerator
{
public static CodeTypeDeclaration GenerateBuilder(Type destType)
{
if (destType == null)
throw new ArgumentNullException("destType");
CodeTypeDeclaration builderType = new
CodeTypeDeclaration(destType.Name + "Builder");
builderType.TypeAttributes = TypeAttributes.Public;
CodeTypeReference destTypeRef = new CodeTypeReference(destType);
CodeExpression resultExpr = AddResultField(builderType, destTypeRef);
PropertyInfo[] builderProps = destType.GetProperties(
BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo prop in builderProps)
{
AddPropertyBuilder(builderType, resultExpr, prop);
}
AddBuildMethod(builderType, resultExpr, destTypeRef);
return builderType;
}
private static void AddBuildMethod(CodeTypeDeclaration builderType,
CodeExpression resultExpr, CodeTypeReference destTypeRef)
{
CodeMemberMethod method = new CodeMemberMethod();
method.Attributes = MemberAttributes.Public | MemberAttributes.Final;
method.Name = "Build";
method.ReturnType = destTypeRef;
method.Statements.Add(new MethodReturnStatement(resultExpr));
builderType.Members.Add(method);
}
private static void AddPropertyBuilder(CodeTypeDeclaration builderType,
CodeExpression resultExpr, PropertyInfo prop)
{
CodeMemberMethod method = new CodeMemberMethod();
method.Attributes = MemberAttributes.Public | MemberAttributes.Final;
method.Name = prop.Name;
method.ReturnType = new CodeTypeReference(builderType.Name);
method.Parameters.Add(new CodeParameterDeclarationExpression(prop.Type,
"value"));
method.Statements.Add(new CodeAssignStatement(
new CodePropertyReferenceExpression(resultExpr, prop.Name),
new CodeArgumentReferenceExpression("value")));
method.Statements.Add(new MethodReturnStatement(
new CodeThisExpression()));
builderType.Members.Add(method);
}
private static CodeFieldReferenceExpression AddResultField(
CodeTypeDeclaration builderType, CodeTypeReference destTypeRef)
{
const string fieldName = "_result";
CodeMemberField resultField = new CodeMemberField(destTypeRef, fieldName);
resultField.Attributes = MemberAttributes.Private;
builderType.Members.Add(resultField);
return new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), fieldName);
}
}
我认为这应该就是这样做 - 它显然未经测试,但你从这里开始创建一个代码生成(继承自BaseCodeGeneratorWithSite
),它编译一个CodeCompileUnit
填充有类型列表的代码生成器。该列表来自您使用该工具注册的文件类型 - 在这种情况下,我可能只是将其设为文本文件,其中包含您要为其生成构建器代码的类型的行分隔列表。让工具扫描它,加载类型(可能必须先加载程序集),然后生成字节码。
这很难,但不像听起来那么难,当你完成后,你将能够编写如下代码:
Paint p = new PaintBuilder().Red(0.4).Blue(0.2).Green(0.1).Build().Mix.Stir();
我相信这几乎正是您想要的。调用代码生成所需要做的就是使用自定义扩展名注册工具(比如说.buildertypes
),在项目中放置一个具有该扩展名的文件,并在其中放置一个类型列表:
MyCompany.MyProject.Paint
MyCompany.MyProject.Foo
MyCompany.MyLibrary.Bar
等等。保存的时候会自动生成你需要的代码文件,支持上面写语句。
我以前曾将这种方法用于具有数百种不同消息类型的高度复杂的消息传递系统。总是构建消息、设置一堆属性、通过通道发送、从通道接收、序列化响应等花费了太长时间......使用代码生成器大大简化了工作,因为它使我能够生成一个单个消息传递类,它将所有单独的属性作为参数并返回正确类型的响应。这不是我会推荐给每个人的东西,但是当您处理非常大的项目时,有时您需要开始发明自己的语法!