7


序章

私のアプリケーションは、メソッドチェーンを使用してオブジェクトをインスタンス化するため、次のように生成および構成されます。

var car = new Car("Ferrari").Doors(2).OtherProperties(x = x.Color("Red"));


問題

実行時にこのオブジェクトを動的に生成する必要があります。構成に必要な連鎖メソッドは実行時に決定されるため、すべてをその場で動的にアセンブルする必要があります。私は過去にリフレクションを使用して次のような単純なオブジェクトを作成しましたnew Car("Ferrari", 2, "Red")が、ラムダ式をパラメーターとして含むチェーンメソッドを使用したことはありません. 私は式ツリーを調べましたが、これは動的な式パラメーターを作成するためのソリューションの一部であると信じていますが、それをリフレクションと組み合わせて基本オブジェクトと追加の連鎖メソッドを作成する方法を見つけようとして完全に行き詰まっています。


感謝と感謝

時間を割いて私の問題を調べ、あなたが提供できるかもしれないガイダンスや情報について事前に.


更新: 結論

dasblinkenlight と Jon Skeet の回答に感謝します。dasblinkenlight の回答を選んだのは、彼のコード サンプルを見てすぐに実行できるようになったからです。Invoke()メソッドチェーンについては、基本的に受け入れられた回答で同じループアプローチを使用したため、そのコードは繰り返しませんが、以下は式ツリーメソッド呼び出しをアクションデリゲートに動的に変換するために書いたコードであり、次に概説されているようにリフレクションを介して実行できますdasblinkenlightの答え。ジョンが指摘したように、これが本当に問題の核心でした。

メソッドのメタデータを格納するヘルパー クラス。

public struct Argument
    {
        public string TypeName;
        public object Value;
    }

public class ExpressionTreeMethodCall
{
    public string MethodName { get; set; }
    public IList<Argument> Arguments { get; set; }

    public ExpressionTreeMethodCall()
    {
        Arguments = new List<Argument>();
    }
}


ラムダ式メソッド呼び出しをアセンブルし、それを別の場所で実行されるアクション デリゲートとして返す静的メソッド (私の場合は引数として渡されますInvoke())。

public static Action<T> ConvertExpressionTreeMethodToDelegate<T>(ExpressionTreeMethodCall methodData)
    {            
        ParameterExpression type = Expression.Parameter(typeof(T));

        var arguments = new List<ConstantExpression>();
        var argumentTypes = new List<Type>();

        foreach (var a in methodData.Arguments)
        {
            arguments.Add(Expression.Constant(a.Value));
            argumentTypes.Add(Type.GetType(a.TypeName));
        }

        // Creating an expression for the method call and specifying its parameter.
        MethodCallExpression methodCall = Expression.Call(type, typeof(T).GetMethod(methodData.MethodName, argumentTypes.ToArray()), arguments);

        return Expression.Lambda<Action<T>>(methodCall, new[] { type }).Compile();
    }
4

2 に答える 2

4

あなたは2つの別々の問題に直面しています:

  • 連鎖メソッドの呼び出し、および
  • ラムダをパラメーターとして受け取るメソッドの呼び出し

2つを別々に扱いましょう。

次の情報が利用可能であるとします。

  • ConstructorInfoチェーンの最初のアイテムを表す (コンストラクター)
  • コンストラクターのパラメーターを表すオブジェクトの配列
  • オブジェクトの配列MethodInfo- 連鎖関数ごとに 1 つ
  • 各連鎖関数のパラメーターを表すオブジェクト配列の配列

結果を構築するプロセスは次のようになります。

ConstructorInfo constr = ...
object[] constrArgs = ...
MethodInfo[] chainedMethods = ...
object[][] chainedArgs = ...
object res = constr.Invoke(constrArgs);
for (int i = 0 ; i != chainedMethods.Length ; i++) {
    // The chaining magic happens here:
    res = chainedMethods[i].Invoke(res, chainedArgs[i]);
}

ループが終了するとres、設定済みのオブジェクトが含まれます。

上記のコードは、チェーンされたメソッドの中にジェネリック メソッドがないことを前提としています。一部のメソッドがたまたまジェネリックである場合は、を呼び出す前に、ジェネリック メソッドの呼び出し可能なインスタンスを作成する追加の手順が必要になりますInvoke

次に、ラムダを見てみましょう。メソッドに渡されるラムダの型に応じて、特定のシグネチャを持つデリゲートを渡す必要があります。System.Delegateクラスを使用して、メソッドを呼び出し可能なデリゲートに変換できるはずです。必要なデリゲートを実装するサポート メソッドを作成する必要がある場合があります。リフレクションを通じて呼び出すことができる必要がある正確なメソッドを見ずに、その方法を言うのは困難です。Func<...>式ツリーを使用して、コンパイル後にインスタンスを取得する必要がある場合があります。の呼び出しは次のx.Color("Red")ようになります。

Expression arg = Expression.Parameter(typeof(MyCarType));
Expression red = Expression.Constant("Red");
MethodInfo color = typeof(MyCarType).GetMethod("Color");
Expression call = Expression.Call(arg, color, new[] {red});
var lambda = Expression.Lambda(call, new[] {arg});
Action<MyCarType> makeRed = (Action<MyCarType>)lambda.Compile();
于 2012-10-27T22:23:57.500 に答える
2

ただし、ラムダ式をパラメータとして含む連鎖メソッドを使用することは決してありません

まあ、チェーンされたメソッドはビットです。これは、リフレクションを数回使用するだけの問題です。連鎖するには

foo.X().Y()

必要がある:

  • X宣言された型からメソッドを取得するfoo
  • の値をfoo呼び出し対象としてリフレクション経由でメソッドを呼び出し、結果を記憶する (例: tmp)
  • 戻り値の型Y宣言された型からメソッドを取得します ( を参照)XMethodInfo.ReturnType
  • tmp前の結果 ( ) を呼び出しターゲットとしてリフレクション経由でメソッドを呼び出す

ラムダ式はより難しく、最初に式をどのように提供するかによって異なります。合理的に任意の式を実行するデリゲートを構築することは、式ツリーを使用してから を呼び出すことはそれほど難しくありませんLambdaExpression.Compileが、何をしているのかを知る必要があります。

于 2012-10-27T21:57:39.590 に答える