Почему вы используете Expression <Func <T>>, а не Func <T>?

Я понимаю лямбда и делегаты Func и Action. Но выражения пеньют меня. В каких обстоятельствах вы использовали бы Expression<Func<T>>, а не простой старый Func<T>?

+891
27 апр. '09 в 13:50
источник поделиться
9 ответов

Если вы хотите рассматривать лямбда-выражения как деревья выражений и заглядывать внутрь них вместо их выполнения. Например, LINQ to SQL получает выражение и преобразует его в эквивалентный оператор SQL и отправляет его на сервер (вместо выполнения лямбда).

Концептуально Expression<Func<T>> полностью отличается от Func<T>. Func<T> обозначает delegate, который в значительной степени является указателем на метод, а Expression<Func<T>> обозначает структуру данных дерева для лямбда-выражения. Эта древовидная структура описывает то, что выражение лямбда делает, а не делает фактическую вещь. Он в основном содержит данные о составе выражений, переменных, вызовах методов,... (например, он содержит информацию, такую ​​как эта лямбда - это константа + некоторый параметр). Вы можете использовать это описание, чтобы преобразовать его в фактический метод (с помощью Expression.Compile) или сделать с ним другие вещи (например, пример LINQ to SQL). Акт лечения лямбда как анонимных методов и деревьев выражений - это всего лишь компиляция.

Func<int> myFunc = () => 10; // similar to: int myAnonMethod() { return 10; }

будет эффективно компилировать метод IL, который ничего не получает и возвращает 10.

Expression<Func<int>> myExpression = () => 10;

будет преобразован в структуру данных, которая описывает выражение, которое не получает параметров и возвращает значение 10:

Выражение против Func увеличенное изображение

В то время как оба они выглядят одинаково во время компиляции, то, что генерирует компилятор, полностью отличается.

+1084
27 апр. '09 в 13:52
источник

Я добавляю ответ для новичков, потому что эти ответы казались мне над головой, пока я не понял, насколько это просто. Иногда ваше ожидание того, что это сложно, делает вас неспособным "обернуть голову вокруг этого".

Мне не нужно было понимать разницу, пока я не наткнулся на действительно раздражающую "ошибку", пытаясь использовать LINQ-to-SQL в общем:

public IEnumerable<T> Get(Func<T, bool> conditionLambda){
  using(var db = new DbContext()){
    return db.Set<T>.Where(conditionLambda);
  }
}

Это прекрасно работало, пока я не начал получать исключения OutofMemoryException для больших наборов данных. Установка точек останова внутри лямбды заставила меня понять, что он перебирает каждую строку в моей таблице один за другим в поисках совпадений с моим состоянием лямбды. Это поставило меня в тупик на какое-то время, потому что, черт возьми, он обрабатывает мою таблицу данных как гигантский IEnumerable вместо того, чтобы делать LINQ-to-SQL, как положено? То же самое делалось и с моим коллегой из LINQ-to-MongoDb.

Исправление состояло в том, чтобы просто превратить Func<T, bool> в Expression<Func<T, bool>>, поэтому я подумал, почему ему нужно Expression а не Func, и в конечном итоге здесь.

Выражение просто превращает делегата в данные о себе. Таким образом, a => a + 1 становится чем-то вроде "С левой стороны есть int a. С правой стороны вы добавляете 1 к нему". Это. Вы можете идти домой сейчас. Это, очевидно, более структурировано, чем это, но это, по сути, все дерево выражений на самом деле - ничего, чтобы обернуть голову вокруг.

Понимая это, становится ясно, почему LINQ-to-SQL нуждается в Expression, а Func не подходит. Func не несет в себе способа проникнуть внутрь себя, чтобы понять, как перевести его в запрос SQL/MongoDb/other. Вы не можете видеть, делает ли он сложение, умножение или вычитание. Все, что вы можете сделать, это запустить его. Expression другой стороны, Expression позволяет вам заглянуть внутрь делегата и увидеть все, что он хочет сделать. Это позволяет вам перевести делегата во что угодно, например, в SQL-запрос. Func не работал, потому что мой DbContext был слеп к содержанию лямбда-выражения. Из-за этого он не мог превратить лямбда-выражение в SQL; тем не менее, он сделал следующую лучшую вещь и повторил это условие по каждой строке в моей таблице.

Изменение: изложив мое последнее предложение по просьбе Джона Питера:

IQueryable расширяет IEnumerable, поэтому методы IEnumerable, такие как Where() получают перегрузки, которые принимают Expression. Когда вы передаете Expression этому, вы сохраняете IQueryable в результате, но когда вы передаете Func, вы отбрасываете базовый IEnumerable, и в результате вы получаете IEnumerable. Другими словами, не замечая, что вы превратили свой набор данных в список для повторения, а не для запроса. Трудно заметить разницу, пока вы действительно не заглянете под капот на подписи.

+298
05 янв. '16 в 8:04
источник
другие ответы

Связанные вопросы


Похожие вопросы

Чрезвычайно важным соображением при выборе Expression vs Func является то, что провайдеры IQueryable, такие как LINQ to Entities, могут "переваривать" то, что вы передаете в выражении, но будете игнорировать то, что вы передаете в Func. У меня есть две записи в блогах по теме:

Подробнее о Expression vs Func с платформой Entity Framework и Влюбленность в LINQ - Часть 7: Выражения и Funcs (последний раздел)

+97
11 янв. '12 в 15:57
источник

Я хотел бы добавить некоторые примечания о различиях между Func<T> и Expression<Func<T>>:

  • Func<T> - это обычная старая школа MulticastDelegate;
  • Expression<Func<T>> является представлением лямбда-выражения в форме дерева выражений;
  • дерево выражений может быть построено с помощью синтаксиса выражения лямбда или с помощью синтаксиса API;
  • дерево выражений может быть скомпилировано делегату Func<T>;
  • теоретически возможно обратное преобразование, но это своего рода декомпиляция, для этого нет встроенных функций, поскольку это не простой процесс;
  • Дерево выражений можно наблюдать/переводить/модифицировать с помощью ExpressionVisitor;
  • методы расширения для IEnumerable работают с Func<T>;
  • методы расширения для IQueryable работают с Expression<Func<T>>.

Вот статья, в которой описываются детали с образцами кода:
LINQ: Func <T> vs. Expression < Func <T → .

Надеюсь, это будет полезно.

+73
11 июн. '13 в 0:02
источник

Об этом есть более философское объяснение из книги Кшиштофа Квалины (Руководство по разработке структуры: соглашения, идиомы и шаблоны для многократно используемых библиотек .NET);

Rico Mariani

Изменить для версии без изображения:

В большинстве случаев вам понадобится Func или Action, если все, что нужно, это запустить какой-то код. Вы должны использовать выражение, когда код должен быть проанализирован, сериализован или оптимизирован перед его запуском. Выражение для размышления о коде, Func/Action для его запуска.

+65
11 мар. '16 в 13:10
источник

LINQ - это канонический пример (например, общение с базой данных), но, по правде говоря, каждый раз, когда вы больше заботитесь о том, чтобы выразить, что делать, чем о том, что вы делаете. Например, я использую этот подход в стеке RPC protobuf-net (чтобы избежать генерации кода и т.д.) - поэтому вы вызываете метод с помощью:

string result = client.Invoke(svc => svc.SomeMethod(arg1, arg2, ...));

Это деконструирует дерево выражений для разрешения SomeMethod (и значения каждого аргумента), выполняет вызов RPC, обновляет любые аргументы ref/out и возвращает результат удаленного вызова. Это возможно только через дерево выражений. Я расскажу об этом подробнее здесь.

Другой пример - когда вы вручную строите деревья выражений с целью компиляции в лямбду, как это делается с помощью универсального кода операторов.

+37
27 апр. '09 в 14:13
источник

Вы должны использовать выражение, если хотите обрабатывать свою функцию как данные, а не как код. Вы можете сделать это, если хотите манипулировать кодом (как данными). В большинстве случаев, если вы не видите необходимости в выражениях, вам, вероятно, не нужно его использовать.

+19
27 апр. '09 в 13:53
источник

Основная причина заключается в том, что вы не хотите запускать код напрямую, а скорее хотите его проверить. Это может быть по нескольким причинам:

  • Сопоставление кода с другой средой (например, код С# для SQL в платформе Entity Framework)
  • Замена частей кода во время выполнения (динамическое программирование или даже простые методы СУХОЙ)
  • Проверка кода (очень полезно при эмуляции скриптов или при анализе)
  • Сериализация - выражения могут быть сериализованы довольно легко и безопасно, делегаты не могут
  • Сильно-типизированная безопасность на вещах, которые по своей сути строго не типизированы, и использование проверок компилятора, даже если вы выполняете динамические вызовы во время выполнения (ASP.NET MVC 5 с Razor - хороший пример)
+17
26 мар. '14 в 12:54
источник

Я еще не вижу ответов, говорящих о производительности. Передача Func<> в Where() или Count() плоха. Действительно плохо. Если вы используете Func<>, тогда он вызывает IEnumerable материал LINQ вместо IQueryable, что означает, что целые таблицы втягиваются и затем фильтруются. Expression<Func<>> значительно быстрее, особенно если вы запрашиваете базу данных, которая живет на другом сервере.

+11
16 июн. '17 в 15:58
источник

Посмотрите другие вопросы по меткам или Задайте вопрос