Если вы действительно хотите иметь возможность связывать настройки свойств без необходимости писать тонну кода, один из способов сделать это - использовать генерацию кода (CodeDom). Вы можете использовать Reflection, чтобы получить список изменяемых свойств, сгенерировать плавный класс построителя с финальным Build()
методом, который возвращает класс, который вы фактически пытаетесь создать.
Я собираюсь пропустить весь материал о том, как зарегистрировать пользовательский инструмент - это довольно легко найти документацию, но все еще долго, и я не думаю, что я бы добавил много, включив его. Я покажу вам, что я думаю о кодегене.
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);
}
}
Я думаю, что это как раз и должно быть сделано - это, очевидно, не проверено, но вы идете отсюда, что вы создаете codegen (наследующий от 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
И так далее. Когда вы сохраните файл, он автоматически сгенерирует необходимый вам кодовый файл, который поддерживает написание операторов, подобных приведенному выше.
Ранее я использовал этот подход для очень запутанной системы обмена сообщениями с несколькими сотнями различных типов сообщений. Потребовалось слишком много времени, чтобы создать сообщение, задать несколько свойств, отправить его по каналу, получить от канала, сериализовать ответ и т. Д. Использование кодгена значительно упростило работу, поскольку позволило мне сгенерировать класс единого сообщения, который принимает все отдельные свойства в качестве аргументов и возвращает ответ правильного типа. Это не то, что я рекомендую всем, но когда вы имеете дело с очень большими проектами, иногда вам нужно начинать придумывать собственный синтаксис!