深入了解表达式






4.90/5 (57投票s)
深入了解表达式。
引言
本文简要介绍 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日:初始发布