Я манекен у цьому сценарії.
Я намагався прочитати в Google, що це, але я просто не розумію. Хтось може дати мені просте пояснення того, що вони є і чому вони корисні?
редагувати: Я говорю про функцію LINQ у .Net.
Я манекен у цьому сценарії.
Я намагався прочитати в Google, що це, але я просто не розумію. Хтось може дати мені просте пояснення того, що вони є і чому вони корисні?
редагувати: Я говорю про функцію LINQ у .Net.
Відповіді:
Найкраще пояснення про дерева виразів, яке я коли-небудь читав, - це стаття Чарлі Калверта.
Підсумовуючи це;
Дерево виразів представляє те, що ви хочете зробити, а не те, як ви хочете це зробити.
Розглянемо такий простий лямбда-вираз:
Func<int, int, int> function = (a, b) => a + b;
Це твердження складається з трьох розділів:
- Декларація:
Func<int, int, int> function
- Оператор рівних:
=
- Лямбда-вираз:
(a, b) => a + b;
Змінна
function
вказує на вихідний виконуваний код, який знає, як скласти два числа .
Це найважливіша різниця між делегатами та виразами. Ви телефонуєте function
(а 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 буде набагато простіше, ніж перекласти необроблений ІЛ чи виконуваний код у SQL. Щоб дещо перебільшити складність проблеми, просто уявіть спробу перевести ряд нулів і одиниць в SQL!
Коли настав час перекласти вираз вашого запиту в SQL, дерево виразів, що представляє ваш запит, розбирається та аналізується, як ми розібрали наше просте дерево лямбда-виразів у попередньому розділі. Звичайно, алгоритм синтаксичного аналізу дерева виразів LINQ до 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 і спрямовувати його на будь-яке джерело даних. Ви не можете мати запит "Інтегрована мова" без нього.
Довга відповідь: Як ви, мабуть, знаєте, під час компіляції вихідного коду ви перетворюєте його з однієї мови на іншу. Зазвичай від мови високого рівня (C #) до нижнього важеля (IL).
В основному це можна зробити двома способами:
Останнє і роблять усі програми, які ми знаємо як «компілятори».
Після створення дерева синтаксичного аналізу ви можете легко перекласти його на будь-яку іншу мову, і це те, що дозволяють нам дерева виразів. Оскільки код зберігається як дані, ви можете робити з ним все, що завгодно, але, ймовірно, ви просто захочете перекласти його на іншу мову.
Тепер у LINQ to SQL дерева виразів перетворюються на команду SQL, а потім надсилаються по дроту на сервер баз даних. Наскільки я знаю, вони не роблять нічого по-справжньому химерного при перекладі коду, але могли б . Наприклад, постачальник запитів може створювати різний код SQL залежно від мережевих умов.
IIUC, дерево виразів схоже на абстрактне дерево синтаксису, але вираз зазвичай має одне значення, тоді як AST може представляти цілу програму (з класами, пакетами, функцією, операторами тощо)
У будь-якому випадку, для виразу (2 + 3) * 5 дерево має вигляд:
*
/ \
+ 5
/ \
2 3
Оцініть кожен вузол рекурсивно (знизу вгору), щоб отримати значення в кореневому вузлі, тобто значення виразу.
Звичайно, ви можете мати унарні (заперечення) або тринарні (if-then-else) оператори, а також функції (n-ary, тобто будь-яку кількість операцій), якщо ваша мова виразів це дозволяє.
Оцінка типів і контроль типу здійснюється на схожих деревах.
Дерева виразів DLR є доповненням до C # для підтримки динамічного виконання мови (DLR). DLR також є тим, що відповідає за надання нам методу "var" оголошення змінних. ( var objA = new Tree();
)
По суті, Microsoft хотіла відкрити CLR для динамічних мов, таких як LISP, SmallTalk, Javascript тощо. Для цього їм була необхідна можливість аналізу та оцінки виразів на льоту. Це було неможливо до появи DLR.
Повертаючись до мого першого речення, дерева виразів є доповненням до C #, що відкриває можливість використання DLR. До цього C # був набагато статичнішою мовою - усі типи змінних повинні були оголошуватися як певний тип, а весь код повинен був бути написаний під час компіляції.
Використовуючи його з
деревами виразу даних, відкриваються шлюзи для динамічного коду.
Скажімо, наприклад, що ви створюєте сайт нерухомості. На етапі проектування ви знаєте всі фільтри, які можна застосувати. Для реалізації цього коду у вас є два варіанти: ви можете написати цикл, який порівнює кожну точку даних із низкою перевірок If-Then; або ви можете спробувати створити запит динамічною мовою (SQL) і передати його програмі, яка може виконати пошук для вас (база даних).
За допомогою дерев виразів тепер ви можете змінювати код у своїй програмі - на льоту - та виконувати пошук. Зокрема, ви можете зробити це через LINQ.
(Докладніше: MSDN: Як: використовувати дерева виразів для побудови динамічних запитів ).
Крім даних
Основне використання дерев виразів - для управління даними. Однак їх також можна використовувати для динамічно генерованого коду. Отже, якщо вам потрібна функція, яка визначається динамічно (ala Javascript), ви можете створити дерево виразів, скомпілювати його та оцінити результати.
Я хотів би піти трохи глибше, але цей сайт робить набагато кращу роботу:
Перелічені приклади включають створення загальних операторів для типів змінних, лямбда-вирази, що рухаються вручну, високоефективне дрібне клонування та динамічне копіювання властивостей читання / запису з одного об'єкта на інший.
Короткі
дерева виразів - це подання коду, який компілюється та обчислюється під час виконання. Вони дозволяють використовувати динамічні типи, що корисно для обробки даних та динамічного програмування.
var
є синтаксичним цукром під час компіляції - він не має нічого спільного з деревами виразів, DLR або часом роботи. var i = 0
отримує компіляцію, як якщо б ви писали int i = 0
, тому ви не можете використовувати var
для представлення типу, який невідомий під час компіляції. Дерева виразів не є "доповненням до підтримки DLR", вони введені в .NET 3.5, щоб дозволити LINQ. З іншого боку, DLR введено в .NET 4.0, щоб дозволити динамічні мови (наприклад, IronRuby) та dynamic
ключове слово. Дерева виразів насправді використовуються DLR для забезпечення взаємодії, це не навпаки.
Дерево виразів, яке ви посилаєтесь, є деревом оцінки виразів?
Якщо так, то це дерево, побудоване парсером. Парсер використовував Lexer / Tokenizer для ідентифікації Токенів із програми. Синтаксичний аналізатор будує бінарне дерево з лексем.
Ось детальне пояснення