大量のコードを記述せずにプロパティ設定をチェーンできるようにしたい場合、これを行う1つの方法は、コード生成(CodeDom)を使用することです。Reflectionを使用して、可変プロパティのリストを取得し、Build()
実際に作成しようとしているクラスを返すfinalメソッドを使用して流暢なビルダークラスを生成できます。
カスタムツールの登録方法に関する定型的な内容はすべてスキップします。ドキュメントを見つけるのはかなり簡単ですが、それでも時間がかかり、それを含めることで多くを追加することはないと思います。私が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
ものをコンパイルするcodegen(から継承)を作成することです。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
等々。保存すると、上記のようなステートメントの記述をサポートする必要なコードファイルが自動的に生成されます。
私は以前、数百の異なるメッセージタイプを持つ非常に複雑なメッセージングシステムにこのアプローチを使用しました。常にメッセージを作成し、一連のプロパティを設定し、チャネルを介して送信し、チャネルから受信し、応答をシリアル化するなど、時間がかかりすぎました。codegenを使用すると、生成が可能になるため、作業が大幅に簡素化されました。個々のプロパティをすべて引数として受け取り、正しいタイプの応答を吐き出す単一のメッセージングクラス。これは私がすべての人に推奨するものではありませんが、非常に大規模なプロジェクトを扱っている場合は、独自の構文を考案し始める必要がある場合があります。