Я не уверен точно, что вы просите; но, по крайней мере, я могу попытаться дать некоторое представление о разнице между Expression
и лямбда-функциями, которые являются языковыми особенностями основных языков .NET (C #, VB.NET).
По существу, лямбда-функция - это анонимная (неназванная) функция, которая может быть объявлена внутри области действия другой функции. В C # (языковая версия 3) это будет объявлено так:
(
список параметров )
=>
тело функции
Например:
void Foo()
{
// two equivalent lambda functions with one double parameter 'x' each:
var square = (double x) => x * x;
var square2 = (double x) => { return x * x; };
// lambda function that doesn't take any arguments:
var doSomething = () => { Console.WriteLine("Hello."); }
}
(обратите внимание, что, например, square
- это не имя лямбда-функции, а переменная, которая на нее ссылается. Сама лямбда-функция не имеет никакого имени, как у метода void Foo()
!)
Нечто очень похожее уже было возможно в C # версии 2, называемой анонимными делегатами :
delegate(double x) { return x * x; } // is equivalent to: (double x) => x * x
Выражения были добавлены в .NET с LINQ (в пространстве имен System.Linq.Expressions
). По сути, это простые структуры данных, которые описывают, как абстрактное синтаксическое дерево (AST), любые вычисления, которые могут быть выражены, например. с C # или VB.NET. Взять например:
// case 1. IEnumerable<int>:
IEnumerable<int> xs = ...;
IEnumerable<int> queryXs = from x in xs where x > 0 select x;
// case 2. IQueryable<int>:
IQueryable<int> ys = ...;
IEnumerable<int> queryYs = from y in ys where y > 0 select y;
Эти два запроса идентичны, за исключением того, что ys
имеет тип IQueryable<int>
вместо IEnumerable<int>
. Компилятор C # переведет вышеупомянутые два запроса в другой код, а именно:
// case 1. IEnumerable<int>:
IQueryable<int> xs = ...;
IEnumerable<int> queryXs = xs.Where<int>(delegate (int x) { return x > 0; });
// case 2. IQueryable<int>:
IEnumerable<int> ys = ...;
ParameterExpression yParameter = Expression.Parameter(typeof(int), "y");
IEnumerable<int> queryYs.Where<int>(
Expression.Lambda<Func<int, bool>>(
Expression.GreaterThan(
yParameter,
Expression.Constant(0, typeof(int))),
new ParameterExpression[] { yParameter }));
Если вы изучите последнее, вы увидите, что для запроса IQueryable<int>
компилятор вывел проанализированный запрос в виде структуры данных, которая описывает запрос. С другой стороны, для запроса к IEnumerable<int>
он просто выводит код, который выполняет запрос. Оказывается, что компилятор делает особую магию, создавая то, что он анализировал как дерево выражений только с типом IQueryable<T>
.
Таким образом,
Expressions и IQueryable<T>
позволяют анализировать запрос во время выполнения и выполнять его оптимизацию и другие преобразования перед его фактическим выполнением. Это интенсивно используется, например, LINQ to SQL для написания интеллектуальных, оптимизированных SQL-запросов:
Представьте, что в базе данных запрашиваются все строки, в которых столбец x
равен> 0.
Если бы база данных была IEnumerable<int>
(это действительно сильно упрощает, я знаю, но терпите меня), все строки должны быть извлечены, а затем отфильтрованы методом .Where(delegate (int x) { ... })
.
Если база данных была IQueryable<int>
, вывод Expression
компилятором будет проанализирован и преобразован в SQL-запрос во время выполнения, так что он будет включать соответствующее предложение WHERE x > 0
и будет производить только нужные строки сразу.
Если вас интересует внутренняя структура / синтаксический анализ деревьев выражений, взгляните на серию подробных блогов Барта де Смета, начиная с Сказки IQueryable - LINQ to LDAP - Часть 0: Введение .