2012-10-27 81 views
6


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

我的應用程序使用方法鏈接因此被生成,並且被配置如下所示實例化的對象:

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


問題

我有一個requiremen t在運行時動態生成此對象 - 配置所需的鏈接方法將在運行時確定,因此所有內容都必須動態組合。我在過去使用反射來創建簡單的對象,如new Car("Ferrari", 2, "Red") - 我很酷 - 但從來沒有任何鏈式方法包含lambda表達式作爲參數 - 這兩個因素真的讓我卡住了。我看着表達式樹和認爲,這是解決創建動態表達參數的一部分,但我完全卡住,試圖找出如何一起縫合與反射創建基礎對象和附加鏈接的方法。


感謝和讚賞

提前抽出時間來看看我的問題,你可能能夠提供任何指導或信息。


UPDATE:結論

非常感謝dasblinkenlight和喬恩斯基特了他們的答案。我選擇了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>(); 
    } 
} 


組裝lambda表達式方法調用,然後返回它作爲在別處執行的動作委託(作爲參數在我的情況下傳遞給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(); 
    } 

回答

2

您所面臨的兩個獨立的問題:

  • 調用鏈的方法,並
  • 調用該採取lambda表達式作爲單獨參數

讓我們處理這兩種方法。

比方說,你準備好以下信息:代表鏈中的第一項

  • 一個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之前,需要額外的步驟來創建通用方法的可調用實例。

現在我們來看看lambda表達式。根據傳遞給方法的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(); 
+0

非常感謝DAS ..所以用你提供的代碼 - 可以在「makeRed」行動對象中的「調用()」在通話過程中存儲在「chainedArgs」和執行鏈接循環? – mmacneil007

+0

@ mmacneil007絕對,這就是主意。 'Action '是一個委託,它可以被存儲在'chainedArgs'數組中的一個數組中,並被傳遞給它相應的方法(在你的情況下,它就是'OtherProperties')。 – dasblinkenlight

+0

非常好,我相信這讓我走上了正確的道路。仍然圍繞表情樹包裹我的頭,但我認爲你在這裏概述的方法應該能夠做到。再次感謝! – mmacneil007

1

,但從來沒有與任何包含lambda表達式作爲參數

嘛,鏈接方法是位鏈接的方法。這只是一個反覆使用反射的問題。鏈接在一起

foo.X().Y() 

您需要:從聲明的類型的foo

    • Get方法X使用foo值作爲通話對象通過反射調用該方法,並記住結果(例如tmp
    • 獲取方法Y來自返回類型的聲明類型X(見MethodInfo.ReturnType
    • 使用以前的結果(tmp)作爲呼叫目標

    Lambda表達式是很難通過反射調用的方法 - 這真的取決於你如何去提供表達首先。使用expression trees然後調用LambdaExpression.Compile來建立委託來執行合理的任意表達式並不難,但是您需要知道自己在做什麼。