If you really want to be able to bind property settings without having to write a ton of code, one way to do this is to use code generation (CodeDom). You can use Reflection to get a list of mutable properties, create a free builder class with the final Build() method, which returns the class that you are actually trying to create.
I'm going to skip all the template files on how to register a custom tool - which is pretty easy to find documentation, but still durable, and I don't think I will add it by adding it. I will show you what I think of codegen though.
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); } }
I think this was just to do it - it is clearly not verified, but here you go from here that you create a codegen (inheriting from BaseCodeGeneratorWithSite ) that compiles CodeCompileUnit populated with a list of types. This list comes from the type of file that you register with the tool - in this case, I will probably just make it a text file with a limited list of types for which you want to create a builder code. Ask the tool to verify this, load the types (maybe load the assemblies first), and generate the bytecode.
It is difficult, but not as difficult as it seems, and when you are done, you can write this code:
Paint p = new PaintBuilder().Red(0.4).Blue(0.2).Green(0.1).Build().Mix.Stir();
I believe that this is almost what you want. All you need to do to trigger the code generation is to register the tool with a custom extension (say .buildertypes ), put a file with this extension in your project and put a list of types in it:
MyCompany.MyProject.Paint MyCompany.MyProject.Foo MyCompany.MyLibrary.Bar
And so on. When you save, it will automatically generate the necessary code file, which supports writing statements like the one above.
I used this approach before for a highly messed up system with several hundred different types of messages. Too much time to always compose a message, set a bunch of properties, send it on a channel, receive from a channel, serialize a response, etc. .... using codegen greatly simplified the work, as it allowed me to create one messaging class, which took all the individual properties as arguments and returned the correct type response. This is not something I would recommend to everyone, but when you are dealing with very large projects, sometimes you need to come up with your own syntax!