2013-11-26 182 views
5

我想用表達式樹建立一個lambda表達式。這是lambda表達式的形式,我想創建:評估Lambda表達式作爲表達式樹的一部分

Func<DateTime, string> requiredLambda = dt => 
    { 
     var formattedDate = dt.ToShortDateString(); 

     /** 
     * The logic is not important here - what is important is that I 
     * am using the result of the executed lambda expression. 
     * */ 
     var builder = new StringBuilder(); 
     builder.Append(formattedDate); 
     builder.Append(" Hello!"); 
     return builder.ToString(); 
    }; 

美中不足的是,我不是從頭開始了建設這棵樹 - 格式化邏輯已經交給我的一個實例的形式Expression<Func<DateTime, string>> - 說:

Expression<Func<DateTime, string>> formattingExpression = dt => dt.ToShortDateString(); 

我知道外的表達式樹的我可以叫

formattingExpression.Compile()(new DateTime(2003, 2, 1)) 

到評估表達式 - 但問題是我希望在表達式樹中對進行評估並將其分配到之內 - 這允許我對錶達式樹中的結果執行其他邏輯。

我到目前爲止所做的一切似乎都是在做這次旅行 - 幾乎肯定是誤解了表達樹的工作原理。任何幫助非常感謝!

+0

我不明白。爲什麼不能簡單地在表達式樹中調用'formattingExpression.Compile()'? – svick

回答

3

所以,如果我理解正確的話,你想創建一個使用你的傳遞函數的lambda表達式(表達式),併爲它做一些額外的工作。所以你基本上只想在表達式中使用這個函數。

在這一點上,讓我建議你甚至不使用表達式。你可以創建一個函數,它需要一個參數Func<DateTime, string>並用它來處理一些東西。但是如果你確實需要表達某些東西,我會試着解釋如何構建一個。

在這個例子中,我將創建這個函數:

string MonthAndDayToString (int month, int day) 
{ 
    return "'" + formattingFunction(new DateTime(2013, month, day)) + "'" 
} 

正如你所看到的,我要建立一個Func<int, int, string>然後創建DateTime對象,並把它傳遞給函數,然後進一步改變結果。

Func<DateTime, string> formatting = dt => (...) // as above 

// define parameters for the lambda expression 
ParameterExpression monthParam = Expression.Parameter(typeof(int)); 
ParameterExpression dayParam = Expression.Parameter(typeof(int)); 

// look up DateTime constructor 
ConstructorInfo ci = typeof(DateTime).GetConstructor(new Type[] { typeof(int), typeof(int), typeof(int) }); 

// look up string.Concat 
MethodInfo concat = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string), typeof(string) }); 

// inner call: formatting(new DateTime(2013, month, day)) 
var call = Expression.Call(formatting.Method, Expression.New(ci, Expression.Constant(2013), monthParam, dayParam)); 

// concat: "'" + call + "'" 
var expr = Expression.Call(concat, Expression.Constant("'"), call, Expression.Constant("'")); 

// create the final lambda: (int, int) => expr 
var lambda = Expression.Lambda<Func<int, int, string>>(expr, new ParameterExpression[] { monthParam, dayParam }); 

// compile and execute 
Func<int, int, string> func = lambda.Compile(); 
Console.WriteLine(func(2, 1)); // '01.02.2013 Hello!' 
Console.WriteLine(func(11, 26)); // '26.11.2013 Hello!' 

看着亞歷克斯」的答案後,看來我誤解你的問題,並試圖解決的你在做什麼相反。但它不是太不同將其更改爲你所真正想要做的事:

Func<DateTime, string> formatting = dt => dt.ToShortDateString(); 

ParameterExpression param = Expression.Parameter(typeof(DateTime)); 
MethodInfo concat = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string), typeof(string) }); 

var call = Expression.Call(formatting.Method, param); 
var expr = Expression.Call(concat, Expression.Constant("'"), call, Expression.Constant(" Hello!'")); 
var lambda = Expression.Lambda<Func<DateTime, string>>(expr, new ParameterExpression[] { param }); 

Func<DateTime, string> func = lambda.Compile(); 
Console.WriteLine(func(new DateTime(2013, 02, 01))); 
Console.WriteLine(func(new DateTime(2013, 11, 26))); 

但我仍然認爲,一個正常功能,需要一個Func<DateTime, string>DateTime參數將是一個更容易維護。所以除非你真的需要表達式,請避開它​​們。


爲什麼我仍然不認爲你真的需要這個表達式。考慮下面這個例子:

private Func<DateTime, string> formatting = dt => dt.ToShortDateString(); 
private Func<DateTime, string> formattingLogic = null; 

public Func<DateTime, string> FormattingLogic 
{ 
    get 
    { 
     if (formattingLogic == null) 
     { 
      // some results from reflection 
      string word = "Hello"; 
      string quote = "'"; 

      formattingLogic = dt => 
      { 
       StringBuilder str = new StringBuilder(quote); 
       str.Append(formatting(dt)); 

       if (!string.IsNullOrWhiteSpace(word)) 
        str.Append(" ").Append(word); 

       str.Append(quote); 
       return str.ToString(); 
      }; 
     } 

     return formattingLogic; 
    } 
} 

void Main() 
{ 
    Console.WriteLine(FormattingLogic(new DateTime(2013, 02, 01))); // '01.02.2013 Hello' 
    Console.WriteLine(FormattingLogic(new DateTime(2013, 11, 26))); // '26.11.2013 Hello' 
} 

正如你所看到的,我只是構建格式化邏輯函數一次,懶洋洋地當它尚未確定。這就是反射運行時得到你在函數某處使用的某些值的時候。由於該函數是作爲lambda函數創建的,我們在lambda函數內使用的本地範圍變量會自動捕獲並保持可用狀態。

現在當然你也可以創建它作爲一個表達式來存儲編譯的函數,但是我認爲這樣做更具可讀性和可維護性。

+0

使用'Expression.Block()'之類的東西,可以將lambda表達爲.Net 4.0表達式樹。儘管你不能直接使用C#lambda創建表達式。 – svick

+0

@svick是的,這就是我的意思。現在拿出那部分我的答案,因爲它不是真的有關無論如何:) – poke

+1

偉大的答案 - 不僅你問我問 - 你回答了我沒有問的問題,但*真的*想知道答案(使用一個函數,而不是我的格式化邏輯表達式)。僅供參考 - 我需要表達式的原因是因爲邏輯的構建依賴於反射 - 我需要緩存編譯函數的性能。 – Lawrence

1

您可以使用:

Func<Func<DateTime,string>, DateTime, string> requiredLambda = (f, dt) => 
{ 
    var formattedDate = f.Invoke(dt); 

    /** 
    * The logic is not important here - what is important is that I 
    * am using the result of the executed lambda expression. 
    * */ 
    var builder = new StringBuilder(); 
    builder.Append(formattedDate); 
    builder.Append(" Hello!"); 
    return builder.ToString(); 
}; 

然後你有你的輸入表達式:

Expression<Func<DateTime, string>> formattingExpression = 
    dt => dt.ToShortDateString(); 

而結果:

var result = requiredLambda 
    .Invoke(formattingExpression.Compile(), new DateTime(2003, 2, 1)); 

// 1.2.2003 Hello! 
+1

謝謝你的回答。理想情況下,我希望將'requiredLambda'的類型保存爲'Func ',但如果我找不到解決方法,您的答案肯定會很有用。 – Lawrence