Se si desidera essere in grado di concatenare le impostazioni delle proprietà senza dover scrivere una tonnellata di codice, un modo per farlo sarebbe utilizzare la generazione del codice (CodeDom). Puoi usare Reflection per ottenere un elenco delle proprietà mutabili, generare una classe di build fluente con un metodo finale Build()
che restituisce la classe che stai effettivamente cercando di creare.
Ho intenzione di saltare tutte le informazioni sullo schema di registrazione su come registrare lo strumento personalizzato - è abbastanza facile trovare la documentazione ma ancora prolisso e non penso che aggiungerei molto includendolo . Ti mostrerò comunque cosa sto pensando per il 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);
}
}
credo che questo dovrebbe solo di farlo - è, ovviamente, non testato, ma dove si va da qui è che si crea un codegen (che eredita da BaseCodeGeneratorWithSite
) che compila un CodeCompileUnit
popolato con un elenco dei tipi. Tale elenco deriva dal tipo di file che si registra con lo strumento: in questo caso probabilmente lo trasformo in un file di testo con un elenco delimitato da linee di tipi per il quale si desidera generare il codice builder. Chiedi allo strumento di eseguire la scansione di questo, caricare i tipi (potrebbe essere necessario caricare prima gli assembly) e generare bytecode.
E 'dura, ma non così difficile come sembra, e quando hai finito sarete in grado di scrivere codice come questo:
Paint p = new PaintBuilder().Red(0.4).Blue(0.2).Green(0.1).Build().Mix.Stir();
che credo sia quasi esattamente quello che vuoi.Tutto quello che dovete fare per richiamare la generazione del codice è registrare lo strumento con un'estensione personalizzata (diciamo .buildertypes
), ha messo un file con tale estensione nel progetto, e mettere un elenco dei tipi in esso:
MyCompany.MyProject.Paint
MyCompany.MyProject.Foo
MyCompany.MyLibrary.Bar
E così via. Quando salvi, genererà automaticamente il file di codice che ti serve che supporta istruzioni di scrittura come quella sopra.
Ho usato questo approccio in precedenza per un sistema di messaggistica molto contorto con diverse centinaia di tipi di messaggi diversi. Ci è voluto troppo tempo per costruire sempre il messaggio, impostare un gruppo di proprietà, inviarlo attraverso il canale, ricevere dal canale, serializzare la risposta, ecc ... utilizzando un codegen ha semplificato notevolmente il lavoro in quanto mi ha permesso di generare un classe di messaggistica singola che ha preso tutte le proprietà individuali come argomenti e restituisce una risposta del tipo corretto. Non è qualcosa che consiglierei a tutti, ma quando hai a che fare con progetti molto grandi, a volte devi iniziare a inventare la tua sintassi!
In realtà ho un approccio simile in uno dei miei progetti. –