Я манекен в этом сценарии.
Я пытался прочитать в Google, что это такое, но не понимаю. Может ли кто-нибудь дать мне простое объяснение того, что это такое и почему они полезны?
edit: я говорю о функции LINQ в .Net.
Я манекен в этом сценарии.
Я пытался прочитать в Google, что это такое, но не понимаю. Может ли кто-нибудь дать мне простое объяснение того, что это такое и почему они полезны?
edit: я говорю о функции LINQ в .Net.
Ответы:
Лучшее объяснение деревьев выражений, которое я когда-либо читал, - это статья Чарли Калверта.
Подвести итог;
Дерево выражений представляет то, что вы хотите сделать, а не то, как вы хотите это делать.
Рассмотрим следующее очень простое лямбда-выражение:
Func<int, int, int> function = (a, b) => a + b;
Это заявление состоит из трех разделов:
- Декларация:
Func<int, int, int> function
- Оператор равенства:
=
- Лямбда-выражение:
(a, b) => a + b;
Переменная
function
указывает на необработанный исполняемый код, который умеет складывать два числа .
Это наиболее важное различие между делегатами и выражениями. Вы вызываете function
(a Func<int, int, int>
), даже не зная, что он будет делать с двумя переданными вами целыми числами. Он берет два и возвращает один, это все, что может знать ваш код.
В предыдущем разделе вы видели, как объявить переменную, указывающую на необработанный исполняемый код. Деревья выражений - это не исполняемый код , это форма структуры данных.
Теперь, в отличие от делегатов, ваш код может знать, для чего предназначено дерево выражений.
LINQ предоставляет простой синтаксис для преобразования кода в структуру данных, называемую деревом выражений. Первый шаг - добавить оператор using для представления
Linq.Expressions
пространства имен:
using System.Linq.Expressions;
Теперь мы можем создать дерево выражений:
Expression<Func<int, int, int>> expression = (a, b) => a + b;
Идентичное лямбда-выражение, показанное в предыдущем примере, преобразуется в дерево выражения, объявленное как имеющее тип
Expression<T>
. Идентификаторexpression
не является исполняемым кодом; это структура данных, называемая деревом выражений.
Это означает, что вы не можете просто вызвать дерево выражений, как вы могли бы вызвать делегата, но вы можете проанализировать его. Итак, что может понять ваш код, анализируя переменную expression
?
// `expression.NodeType` returns NodeType.Lambda.
// `expression.Type` returns Func<int, int, int>.
// `expression.ReturnType` returns Int32.
var body = expression.Body;
// `body.NodeType` returns ExpressionType.Add.
// `body.Type` returns System.Int32.
var parameters = expression.Parameters;
// `parameters.Count` returns 2.
var firstParam = parameters[0];
// `firstParam.Name` returns "a".
// `firstParam.Type` returns System.Int32.
var secondParam = parameters[1].
// `secondParam.Name` returns "b".
// `secondParam.Type` returns System.Int32.
Здесь мы видим, что выражение дает нам много информации.
Но зачем нам это нужно?
Вы узнали, что дерево выражений - это структура данных, представляющая исполняемый код. Но до сих пор мы не ответили на главный вопрос, зачем нужно совершать такое преобразование. Это вопрос, который мы задали в начале этого поста, и теперь пора на него ответить.
Запрос LINQ to SQL не выполняется внутри вашей программы C #. Вместо этого он переводится в SQL, отправляется по сети и выполняется на сервере базы данных. Другими словами, следующий код никогда не выполняется внутри вашей программы:
var query = from c in db.Customers where c.City == "Nantes" select new { c.City, c.CompanyName };
Сначала он преобразуется в следующий оператор SQL, а затем выполняется на сервере:
SELECT [t0].[City], [t0].[CompanyName] FROM [dbo].[Customers] AS [t0] WHERE [t0].[City] = @p0
Код, найденный в выражении запроса, должен быть преобразован в запрос SQL, который можно отправить другому процессу в виде строки. В данном случае этим процессом является база данных SQL-сервера. Очевидно, будет намного проще преобразовать структуру данных, такую как дерево выражений, в SQL, чем преобразовать необработанный IL или исполняемый код в SQL. Чтобы несколько преувеличить сложность проблемы, просто представьте, что пытаетесь перевести серию нулей и единиц в SQL!
Когда приходит время перевести выражение вашего запроса в SQL, дерево выражений, представляющее ваш запрос, разбирается и анализируется так же, как мы разобрали наше простое дерево лямбда-выражения в предыдущем разделе. Конечно, алгоритм синтаксического анализа дерева выражений LINQ to SQL намного сложнее, чем тот, который мы использовали, но принцип тот же. После анализа частей дерева выражения LINQ обдумывает их и решает, как лучше всего написать инструкцию SQL, которая вернет запрошенные данные.
Деревья выражений были созданы для решения задачи преобразования кода, такого как выражение запроса, в строку, которую можно передать другому процессу и выполнить там. Это так просто. Здесь нет большой тайны, нет волшебной палочки, которой нужно махать. Нужно просто взять код, преобразовать его в данные, а затем проанализировать данные, чтобы найти составные части, которые будут преобразованы в строку, которую можно передать другому процессу.
Поскольку запрос поступает в компилятор, заключенный в такую абстрактную структуру данных, компилятор может интерпретировать его практически любым способом. Он не обязан выполнять запрос в определенном порядке или определенным образом. Вместо этого он может проанализировать дерево выражений, обнаружить, что вы хотите сделать, а затем решить, как это сделать. По крайней мере теоретически, он может учитывать любое количество факторов, таких как текущий сетевой трафик, нагрузка на базу данных, текущие наборы результатов, которые он имеет, и т. Д. На практике LINQ to SQL не учитывает все эти факторы. , но теоретически он может делать практически все, что хочет. Кроме того, можно передать это дерево выражений в некоторый пользовательский код, который вы пишете вручную, который мог бы проанализировать его и преобразовать во что-то очень отличное от того, что создается LINQ to SQL.
И снова мы видим, что деревья выражений позволяют нам представлять (выражать?) То, что мы хотим сделать. И мы пользуемся услугами переводчиков, которые решают, как наши выражения используются.
Дерево выражений - это механизм преобразования исполняемого кода в данные. Используя дерево выражений, вы можете создать структуру данных, которая представляет вашу программу.
В C # вы можете работать с деревом выражений, созданным лямбда-выражениями, с помощью Expression<T>
класса.
В традиционной программе вы пишете такой код:
double hypotenuse = Math.Sqrt(a*a + b*b);
Этот код заставляет компилятор генерировать присваивание, и все. В большинстве случаев это все, что вас волнует.
В обычном коде ваше приложение не может вернуться назад и посмотреть, hypotenuse
чтобы определить, что оно было создано путем выполнения Math.Sqrt()
вызова; эта информация просто не является частью того, что включено.
Теперь рассмотрим лямбда-выражение, подобное следующему:
Func<int, int, double> hypotenuse = (a, b) => Math.Sqrt(a*a + b*b);
Это немного отличается от прежнего. Теперь hypotenuse
это фактически ссылка на блок исполняемого кода . Если вы позвоните
hypotenuse(3, 4);
вы получите 5
возвращенное значение .
Мы можем использовать деревья выражений для исследования созданного блока исполняемого кода. Попробуйте вместо этого:
Expression<Func<int, int, int>> addTwoNumbersExpression = (x, y) => x + y;
BinaryExpression body = (BinaryExpression) addTwoNumbersExpression.Body;
Console.WriteLine(body);
Это производит:
(x + y)
С деревьями выражений возможны более сложные техники и манипуляции.
Деревья выражений - это представление выражения в памяти, например арифметическое или логическое выражение. Например, рассмотрим арифметическое выражение
a + b*2
Поскольку * имеет более высокий приоритет оператора, чем +, дерево выражений строится следующим образом:
[+]
/ \
a [*]
/ \
b 2
Имея это дерево, его можно оценить для любых значений a и b. Кроме того, вы можете преобразовать его в другие деревья выражений, например, чтобы получить выражение.
Когда вы реализуете дерево выражений, я бы предложил создать Expression базового класса . Производный от этого класс BinaryExpression будет использоваться для всех двоичных выражений, таких как + и *. Затем вы можете ввести VariableReferenceExpression для ссылочных переменных (таких как a и b) и другой класс ConstantExpression (для 2 из примера).
Дерево выражения во многих случаях строится в результате синтаксического анализа входных данных (напрямую от пользователя или из файла). Для оценки дерева выражений я бы предложил использовать шаблон Visitor .
Краткий ответ: приятно иметь возможность написать такой же запрос LINQ и указать его на любой источник данных. Без него у вас не было бы запроса "Language Integrated".
Длинный ответ: как вы, наверное, знаете, когда вы компилируете исходный код, вы переводите его с одного языка на другой. Обычно с языка высокого уровня (C #) на более низкий уровень (IL).
В основном это можно сделать двумя способами:
Последнее - то, что делают все программы, известные нам как «компиляторы».
Если у вас есть дерево синтаксического анализа, вы можете легко перевести его на любой другой язык, и это то, что деревья выражений позволяют нам делать. Поскольку код хранится в виде данных, вы можете делать с ним все, что захотите, но, возможно, вы просто захотите перевести его на какой-нибудь другой язык.
Теперь в LINQ to SQL деревья выражений превращаются в команду SQL, а затем отправляются по сети на сервер базы данных. Насколько я знаю, они не делают ничего особенного при переводе кода, но могли . Например, поставщик запросов может создавать различный код SQL в зависимости от условий сети.
IIUC, дерево выражений похоже на абстрактное синтаксическое дерево, но выражение обычно дает одно значение, тогда как AST может представлять всю программу (с классами, пакетами, функцией, операторами и т. Д.)
В любом случае, для выражения (2 + 3) * 5 дерево будет:
*
/ \
+ 5
/ \
2 3
Оцените каждый узел рекурсивно (снизу вверх), чтобы получить значение в корневом узле, то есть значение выражения.
Конечно, у вас могут быть унарные (отрицание) или тройные (если-то-еще) операторы, а также функции (n-арные, т.е. любое количество операций), если ваш язык выражений позволяет это.
Оценка типов и управление типами выполняется для похожих деревьев.
Деревья выражений DLR являются дополнением к C # для поддержки среды выполнения динамического языка (DLR). DLR также отвечает за предоставление нам метода объявления переменных "var". ( var objA = new Tree();
)
По сути, Microsoft хотела открыть CLR для динамических языков, таких как LISP, SmallTalk, Javascript и т. Д. Для этого им нужно было иметь возможность анализировать и оценивать выражения на лету. Это было невозможно до появления DLR.
Вернемся к моему первому предложению: деревья выражений - это дополнение к C #, открывающее возможность использования DLR. До этого C # был гораздо более статическим языком - все типы переменных нужно было объявлять как определенный тип, и весь код приходилось писать во время компиляции.
Использование с данными
деревьями выражений открывает шлюзы для динамического кода.
Скажем, например, что вы создаете сайт о недвижимости. На этапе проектирования вы знаете все фильтры, которые можно применить. Для реализации этого кода у вас есть два варианта: вы можете написать цикл, который сравнивает каждую точку данных с серией проверок If-Then; или вы можете попытаться создать запрос на динамическом языке (SQL) и передать его программе, которая может выполнить поиск за вас (база данных).
С помощью деревьев выражений теперь вы можете изменять код в своей программе на лету и выполнять поиск. В частности, это можно сделать через LINQ.
(См. Также: MSDN: Как использовать деревья выражений для создания динамических запросов. ).
Помимо данных
В основном деревья выражений используются для управления данными. Однако их также можно использовать для динамически генерируемого кода. Итак, если вам нужна функция, которая определяется динамически (например, Javascript), вы можете создать дерево выражений, скомпилировать его и оценить результаты.
Я бы пошел немного глубже, но этот сайт работает намного лучше:
Деревья выражений как компилятор
Перечисленные примеры включают создание универсальных операторов для типов переменных, лямбда-выражений, изменяемых вручную, высокопроизводительное неглубокое клонирование и динамическое копирование свойств чтения / записи из одного объекта в другой.
Сводные
деревья выражений - это представления кода, который компилируется и оценивается во время выполнения. Они позволяют использовать динамические типы, что полезно для работы с данными и динамического программирования.
var
является синтаксическим сахаром времени компиляции - он не имеет ничего общего с деревьями выражений, DLR или средой выполнения. var i = 0
компилируется, как если бы вы написали int i = 0
, поэтому вы не можете использовать его var
для представления типа, который не известен во время компиляции. Деревья выражений не являются «дополнением для поддержки DLR», они введены в .NET 3.5 для поддержки LINQ. DLR, с другой стороны, представлен в .NET 4.0, чтобы разрешить использование динамических языков (например, IronRuby) и dynamic
ключевое слово. Деревья выражений фактически используются DLR для обеспечения взаимодействия, а не наоборот.
Является ли дерево выражений, на которое вы ссылаетесь, деревом оценки выражений?
Если да, то это дерево, построенное парсером. Parser использовал Lexer / Tokenizer для идентификации токенов из программы. Parser строит двоичное дерево из токенов.
Вот подробное объяснение