Si realmente desea poder encadenar las configuraciones de propiedad sin tener que escribir una tonelada de código, una forma de hacerlo sería usar la generación de código (CodeDom). Puede usar Reflection para obtener una lista de las propiedades mutables, generar una clase de generador con fl uido con un método final Build()
que devuelve la clase que realmente está tratando de crear.
Voy a omitir todas las cosas repetitivas acerca de cómo registrar la herramienta personalizada; es bastante fácil encontrar documentación en ella, pero aún así es larga y no creo que agregue mucho al incluirla . Sin embargo, te mostraré en lo que estoy pensando para el 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);
}
}
creo que esto debería sólo de hacerlo - es, obviamente, no probado, pero ¿dónde ir desde aquí es que se crea un codegen (heredando de BaseCodeGeneratorWithSite
) que compila una CodeCompileUnit
poblada con una lista de tipos. Esa lista proviene del tipo de archivo que registra con la herramienta; en este caso, probablemente solo lo convertiría en un archivo de texto con una lista de tipos delimitada por líneas para la que desea generar código de generador. Haga que la herramienta escanee esto, cargue los tipos (podría tener que cargar los ensamblados primero) y genere bytecode.
Es difícil, pero no es tan difícil como parece, y cuando haya terminado usted será capaz de escribir código como este:
Paint p = new PaintBuilder().Red(0.4).Blue(0.2).Green(0.1).Build().Mix.Stir();
que creo que es casi exactamente lo que quiere.Todo lo que tiene que hacer para invocar la generación de código es registrar la herramienta con una extensión personalizada (digamos .buildertypes
), poner un archivo con esa extensión en su proyecto, y poner una lista de tipos en ella:
MyCompany.MyProject.Paint
MyCompany.MyProject.Foo
MyCompany.MyLibrary.Bar
Y así. Cuando guarde, generará automáticamente el archivo de código que necesita que admita declaraciones de escritura como la anterior.
He utilizado este enfoque antes para un sistema de mensajes altamente intrincado con cientos de tipos de mensajes diferentes. Me tomaba demasiado tiempo construir siempre el mensaje, establecer un conjunto de propiedades, enviarlo a través del canal, recibir del canal, serializar la respuesta, etc. Usar un codegen simplificó enormemente el trabajo ya que me permitió generar un clase de mensajería única que tomó todas las propiedades individuales como argumentos y escupió una respuesta del tipo correcto. No es algo que recomendaría a todos, pero cuando se trata de proyectos muy grandes, a veces es necesario que comiences a inventar tu propia sintaxis.
Actualmente tengo un enfoque similar en uno de mis proyectos. –