簡體   English   中英

使用反射,鏈式方法和lambda表達式動態創建對象

[英]Dynamically create object using reflection, chained methods and lambda expressions


介紹

我的應用程序使用方法鏈實例化一個對象,因此它的生成和配置如下:

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


問題

我需要在運行時動態生成此對象 - 配置所需的鏈接方法將在運行時確定,因此必須動態地動態組裝所有內容。 我過去曾使用反射來創建簡單的對象,如new Car("Ferrari", 2, "Red") - 我很酷 - 但從來沒有任何包含lambda表達式作為參數的鏈式方法 - 這兩個因素真的讓我陷入困境 我已經查看了表達式樹,並且相信這是創建動態表達式參數的解決方案的一部分,但我完全陷入困境,試圖弄清楚如何將它與反射一起縫合以創建基礎對象和其他鏈式方法。


謝謝和贊賞

提前花時間查看我的問題以及您可能提供的任何指導或信息。


更新:結論

非常感謝dasblinkenlight和Jon Skeet的回答。 我選擇了dasblinkenlight的答案,因為他的代碼示例讓我立即開始運行。 對於方法鏈接,我基本上在接受的答案中使用相同的循環方法,所以我不會重復該代碼,但下面是我編寫的代碼,動態地將表達式樹方法調用轉換為動作委托,然后可以通過反射Invoke()執行如dasblinkenlight的回答所述。 正如喬恩指出的那樣,這確實是問題的症結所在。

Helper類存儲方法元數據。

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>();
    }
}


用於組裝lambda表達式方法調用的靜態方法,然后將其作為要在其他位置執行的操作委托返回Invoke()在我的情況下作為參數傳遞給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();
    }

您面臨兩個不同的問題:

  • 調用鏈式方法,和
  • 調用將lambdas作為參數的方法

讓我們分別處理這兩個問題。

假設您有以下信息:

  • 表示鏈中第一個項的ConstructorInfo (構造函數)
  • 表示構造函數參數的對象數組
  • MethodInfo對象的數組 - 每個鏈接函數一個
  • 表示每個鏈接函數的參數的對象數組數組

然后構造結果的過程如下所示:

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之前,需要在制作泛型方法的可調用實例時需要額外的步驟。

現在讓我們來看看lambdas。 根據傳遞給方法的lambda的類型,您需要傳遞具有特定簽名的委托。 您應該能夠使用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();

但是從來沒有任何包含lambda表達式作為參數的鏈式方法

嗯,鏈式方法是有點。 這只是幾次使用反射的問題。 連在一起

foo.X().Y()

你需要:

  • 從聲明的foo類型中獲取方法X
  • 使用foo的值作為調用目標通過反射調用方法,並記住結果(例如tmp
  • 返回類型X的聲明類型中獲取方法Y (請參閱MethodInfo.ReturnType
  • 使用先前結果( tmp )作為調用目標,通過反射調用方法

lambda表達式更難 - 它實際上取決於你將如何首先提供表達式。 構建一個委托來執行一個合理的任意表達式並不是很難使用表達式樹然后調用LambdaExpression.Compile ,但你需要知道你在做什么。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM