評估Lambda表達式作為表達式樹的一部分

c# expression-trees 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)) 

評估表達式 - 但問題是我希望表達式樹中評估並分配它 - 允許我在表達式樹中對結果執行額外的邏輯。

到目前為止,我沒有想到的任何事情似乎都是這樣做的 - 幾乎可以肯定是對錶達樹如何工作的誤解。任何幫助非常感謝!

一般承認的答案

所以,如果我理解正確,你想創建一個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!'

在看了Alex的回答之後,似乎我誤解了你的問題,試圖解決你正在做的事情。但是將它改變為你實際想要做的事情並不是完全不同:

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函數中的局部作用域中使用的變量將被自動捕獲並保持可用。

當然,您現在也可以將其創建為表達式並存儲已編譯的函數,但我認為這樣做更具可讀性和可維護性。


熱門答案

你可以使用:

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!


許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因