65.9K
CodeProject 正在变化。 阅读更多。
Home

深入了解表达式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (57投票s)

2008 年 11 月 1 日

CPOL

5分钟阅读

viewsIcon

175791

downloadIcon

449

深入了解表达式。

引言

本文简要介绍 Expressions 命名空间以及如何手动创建表达式树。

我将在本文中尝试涵盖的内容是:

我想一篇文章的内容够多了。

一点历史

.NET 随着时间的推移不断发展,但委托的使用一直是其中不变的一部分。对于不知道委托是什么的人来说,它们只是方法指针。虽然这与本文内容不直接相关,但我认为展示委托如何演变到 Lambda 表达式,以便您能理解文章的其余部分,这是一个好主意。

以前,您不得不像这样手动创建委托:

    // Declare a delegate type for processing a book:
    public delegate void ProcessBookDelegate(Book book);

    // Maintains a book database.
    public class BookDB
    {
        // Call a passed-in delegate on each paperback book to process it: 
        public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
        {
            foreach (Book b in list)
            {
                if (b.Paperback)
                    // Calling the delegate:
                    processBook(b);
            }
        }
    }

    // Class to test the book database:
    class TestBookDB
    {
        // Print the title of the book.
        static void PrintTitle(Book b)
        {
            System.Console.WriteLine("   {0}", b.Title);
        }

        // Execution starts here.
        static void Main()
        {
            // Create a new delegate object associated with the static 
            // method Test.PrintTitle:
            bookDB.ProcessPaperbackBooks(PrintTitle);

        }
    }	

直接摘自 MSDN (http://msdn.microsoft.com/en-us/library/ms173176(VS.80).aspx)。

幸运的是,多年来,微软为我们提供了匿名委托,允许我们这样做:

this.Loaded += delegate
{
	MessageBox.Show("in the delegate");
};

如果我们必须以非匿名方式声明这个委托,那么它的签名将如下所示:

internal delegate void MessagDelegate();

这都很好,但有时您希望能够创建一个接受参数或甚至返回值的匿名委托。幸运的是,我们仍然可以使用匿名委托来实现这一点,但我们必须确保委托类型在运行时是已知的,所以我们需要提供一个非匿名的委托签名,以便在接受匿名委托的任何方法中使用。这允许我们获得正确的返回类型/值。

这是一个例子。

internal delegate String UpperCaseAStringDelegate(String s);
....
....
this.Loaded += delegate
{
    ShowIt(delegate(string s)
    {
        MessageBox.Show(s);
        return s.ToUpper();
    });
};
....
....
private void ShowIt(UpperCaseAStringDelegate del)
{
    MessageBox.Show(del("hello"));
}

然后过了一段时间,微软引入了 Lambda 语法。有很多关于 Lambda 的优秀文章。我将只展示一个将它们与匿名委托进行比较的小例子,但您会在互联网上找到更多,请自行查看。

使用最后一个例子,我们可以简单地用 Lambda `(s) => {...}` 替换 `delegate(string s) {...}`。这是一个使用 Lambda 语法的重构示例:

internal delegate String UpperCaseAStringDelegate(String s);
....
....
this.Loaded += delegate
{
    ShowIt((s) =>
    {
        MessageBox.Show(s);
        return s.ToUpper();
    });
};
....
....
private void ShowIt(UpperCaseAStringDelegate del)
{
    MessageBox.Show(del("hello"));
}

但我们实际上可以更进一步,消除对 `UpperCaseAStringDelegate` 的依赖。我们可以简单地用通用的 `Func<T,TResult>` 委托之一替换它,这样我们就得到了以下代码:

internal delegate String UpperCaseAStringDelegate(String s);
....
....
this.Loaded += delegate
{
    ShowItLambda((s) =>
    {
        MessageBox.Show(s);
        return s.ToUpper();
    });
};
....
....
private void ShowItLambda(Func<String,String> lambda)
{
    MessageBox.Show(lambda("hello"));
}

是不是更好了。这就是我想在本节中说的全部内容,我只是想让您了解 `Func<T,TResult>` 委托和 Lambda 实际上在做什么,然后再继续讨论创建表达式和表达式命名空间的一些更细微的细节。

创建表达式

既然我们了解了 Lambda 和一些 `Func<T,TResult>` 委托,让我们继续看看表达式。表达式随 Lambda 一起出现。微软对表达式树的说法是:“表达式树将语言级别的代码表示为数据。数据存储在树状结构中。表达式树中的每个节点代表一个表达式,例如方法调用或二元运算,如 x < y。”

这对我们意味着什么。好吧,让我们考虑以下图表,其中有一个通用的 `Func<T,T,TResult>` 委托,这意味着我们有一个接受两个参数并返回一个值的 Lambda。下图展示了一个表达式示例及其在表达式树中的表示。

因此,您可以从中看出我们有 `BinaryExpression` / `ParameterExpression` / `MemberExpression` 等。好的,可以。但具体例子看起来会是怎样的。

我认为最好的方法是向您展示一些例子。我将展示两个例子,它们都基于以下测试数据:

String[] bindings = new String[2] {"NetTcpBinding", "HttpBinding"};
List<Order> orders = new List<Order>{
    new Order {OrderId=1},
    new Order {OrderId=2}
};

示例 1

让我们考虑以下内容:

foreach (String bindingString in
    bindings.Where((x) => x.StartsWith("Net")))
{
    Console.WriteLine("Yields {0}".F(bindingString));
}

这仅仅是在 `Where` 扩展方法中使用 Lambda。但我们可以有不同的做法吗?是的,我们也可以这样做,其中我们有一个 `Expression<Func<String, Boolean>>` 变量,当它在 `where` 扩展方法中使用时,我们必须对其进行编译。您看到所有这些将走向何方了吗?

Expression<Func<String, Boolean>> 
    staticDeclaredExpression = (x) => x.StartsWith("Net");


foreach (String bindingString in
    bindings.Where(staticDeclaredExpression.Compile()))
{
    Console.WriteLine("Yields {0}".F(bindingString));
}

好的,现在您已经理解了表达式的基本思想,也许是时候看一些更难的例子了。

示例 2

在此示例中,我们将手动创建一个 Lambda,它将执行以下操作:`(o) => o.OrderID==2`,用于 `where` 扩展方法。这是代码:

ConstantExpression constOrderId = Expression.Constant(2);

ParameterExpression paramOrder = 
    Expression.Parameter(typeof(Order), "o");

MemberExpression mex = 
    LambdaExpression.PropertyOrField(paramOrder, "OrderId");

BinaryExpression filter = Expression.Equal(mex, constOrderId);
Expression<Func<Order, bool>> exprLambda =
    Expression.Lambda<Func<Order, bool>>(filter, 
        new ParameterExpression[] { paramOrder });


foreach (Order o in orders.Where(exprLambda.Compile()))
{
    ......
}

这里需要一点解释,因为我们正在使用一个对象并希望使用它的一个方法,所以我们需要使用 `MemberExpression`,并且我们需要将其与常量进行测试,所以我们需要使用 `ConstantExpression`。我们还需要测试相等性,所以我们需要使用 `BinaryExpression`。最后,我们需要创建一个整体的表达式树,我们可以使用 `Expression.Lambda<Func<T,TResult>>` 来实现。如果您查看 `Expression.Lambda` 方法,它可能会开始变得更有意义。

示例 3

最后一个示例将尝试执行以下操作:`(x) => x.Length > 1`,其中 `x` 是一个 `String`。

ConstantExpression constLength = Expression.Constant(1);

ParameterExpression stringParameter = 
    Expression.Parameter(typeof(String), "s");

MemberExpression stringMember = 
    LambdaExpression.PropertyOrField(stringParameter, "Length");


Expression<Func<String, Boolean>> bindLambda =
    Expression.Lambda<Func<String, Boolean>>
    (
        Expression.GreaterThan(
        stringMember,
        constLength),
        stringParameter
    );


foreach (String bindingString in 
    bindings.Where(bindLambda.Compile()))
{
    ....
}

这与最后一个示例大体相同,除了我使用以下 `Expression.Lambda()` 方法构建了整个树,我实际上一次性构建了整个表达式树。

public static Expression<TDelegate> Lambda<TDelegate>(
    Expression body,
    IEnumerable<ParameterExpression> parameters
)

表达式命名空间

如果我们查看 `System.Linq.Expressions` 命名空间中的一些可用类,我们可以很好地了解可以通过编程实现什么。

涵盖所有这些类超出了本文的范围。如果您想做更多,请尽管去做。玩得开心。

我们完成了

如果您喜欢这篇文章,请投票。谢谢。

历史

  • 2008年11月1日:初始发布
© . All rights reserved.