2014-02-05 67 views
3

考慮下面的一組類通過:獲取參考參數中一個lambda作爲函數求

public class MyClass 
{ 
    public int MyInt { get; set; } 
}  

public class ObjectProcessor 
{ 
    public int ProcessObject(MyClass myClass) 
    { 
     return myClass.MyInt ++; 
    } 
} 

public class Runner 
{ 
    public void Run() 
    { 
     var classToPass = new MyClass(); 

     FuncExecutor.ExecuteAction<MyClass>(x => x.ProcessObject(classToPass)); 
    } 
} 

public static class FuncExecutor 
{ 
    public static void ExecuteAction<T>(Expression<Func<ObjectProcessor, int>> expression) 
    { 
     // var func = expression.Compile(); ... does having an Expression help? 

     // How can I get a reference to 'classToPass' at this point? 

     // The 'classToPass' Type is known to be 'T', in this case 'MyClass'. 
    } 
} 

ExecuteAction方法中,我怎樣才能到中傳遞到ProcessObjectclassToPass實例的引用?

編輯:這些評論突出瞭解析表達樹的複雜性,這些表達樹在其構圖上可能差異很大。

然而,在這種特定情況下有兩個事實,減少這種變化顯着:

  • ProcessObject將只採取單一的參數。
  • 參數類型是預先已知的。

修改代碼來表達這一點。

+0

您可以通過表達式樹進行爬網並查找「ConstantExpression」。如何做到這一點取決於樹的結構可能會有多大。 – SLaks

+3

這可能是其中一種完全不同的方法可能比嘗試使用表達式樹強制解決方案更好的情況之一。 – Dirk

+0

@Dirk,你能進一步解釋你的意思嗎? – Holf

回答

1

要非常明確回答:

public class Runner 
{ 
    public void Run() 
    { 
     var classToPass = new MyClass(); 
     classToPass.MyInt = 42; 

     FuncExecutor.ExecuteAction(x => x.ProcessObject(classToPass)); 
    } 
} 

public class FuncExecutor 
{ 
    public static void ExecuteAction(Expression<Func<ObjectProcessor, int>> expression) 
    { 
     var lambdaExpression = (LambdaExpression)expression; 
     var methodCallExpression = (MethodCallExpression)lambdaExpression.Body; 

     var memberExpression = (MemberExpression)methodCallExpression.Arguments[0]; 
     var constantExpression = (ConstantExpression)memberExpression.Expression; 
     var fieldInfo = (FieldInfo)memberExpression.Member; 

     var myClassReference = (MyClass) fieldInfo.GetValue(constantExpression.Value); 

     Console.WriteLine(myClassReference.MyInt); // prints "42" 
    } 
} 

請注意,當您通過拉姆達到ExecuteAction方法,可以捕獲一個局部變量的引用(classToPass)。編譯器會生成一些代碼來正確處理。更確切地說,它將生成一個類型爲MyClass的單個成員(字段),以保存引用並從此處使用它。這就是爲什麼你會在參數表達式列表中獲得MemberExpression

由於您無法直接操作此生成的類型,因此不能只使用成員表達式Value屬性。但是,您可以使用MemberInfo和目標引用(編譯器生成類型的實例)動態調用成員訪問器。

我不會依賴這段代碼。

你可以閱讀更多關於拉姆達相關的編譯器生成的代碼在這裏,例如:http://thewalkingdev.blogspot.fr/2012/04/c-lambda-expressions-and-closures.html

1

最簡單的方法是通過實例作爲參數,讓ExecuteAction採取調用使用該實例的過程方法的照顧。要做到這一點,必須使用通用對象的處理器接口,讓您的代碼結構的一點點:

public interface IObjectProcessor<T> { 
    public int ProcessObject(T instance); 
} 

public class MyClassProcessor : IObjectProcessor<MyClass> { 
    public int ProcessObject(MyClass myClass) { 
     return myClass.MyInt ++; 
    } 
} 

public class Runner { 
    public void Run() { 
     var classToPass = new MyClass(); 
     var processor = new MyClassProcessor(); 

     FuncExecutor.ExecuteAction<MyClass>(processor, classToPass); 
    } 
} 

public class FuncExecutor { 
    public static void ExecuteAction<T>(IObjectProcessor<T> processor, T obj) { 
     int result = processor.ProcessObject(obj); 
    } 
} 

這種設計可能是一個有點討厭,特別是如果您的處理器是「無狀態」,如果你真的需要一個Func作爲參數。在這種情況下,您可以刪除接口並使用靜態處理器:

public class MyClassProcessor 
    public static int ProcessObject(MyClass myClass) { 
     return myClass.MyInt ++; 
    } 
} 

public class Runner { 
    public void Run() { 
     var classToPass = new MyClass(); 

     FuncExecutor.ExecuteAction<MyClass>(MyClassProcessor.ProcessObject, classToPass); 
    } 
} 

public class FuncExecutor { 
    public static void ExecuteAction<T>(Func<T, int> process, T obj) { 
     int result = process(obj); 
    } 
} 
+0

這與我最終做的非常相似,直到基於純表達式的解決方案發布在不同的答案中。 – Holf