Я понимаю лямбды Func
и Action
делегаты. Но выражения озадачивают меня.
При каких обстоятельствах вы бы использовали Expression<Func<T>>
скорее старый, чем обычный Func<T>
?
c#
delegates
lambda
expression-trees
Ричард Нэгл
источник
источник
Ответы:
Когда вы хотите трактовать лямбда-выражения как деревья выражений и смотреть в них, а не выполнять их. Например, LINQ to SQL получает выражение и преобразует его в эквивалентный оператор SQL и отправляет его на сервер (а не выполняет лямбда-выражение).
Концептуально,
Expression<Func<T>>
это совершенно отличается отFunc<T>
.Func<T>
обозначает a,delegate
который в значительной степени является указателем на метод, иExpression<Func<T>>
обозначает древовидную структуру данных для лямбда-выражения. Эта древовидная структура описывает то, что делает лямбда-выражение, а не делает реальную вещь. Он в основном содержит данные о составе выражений, переменных, вызовов методов, ... (например, он хранит такую информацию, как эта лямбда - некоторая константа + некоторый параметр). Вы можете использовать это описание, чтобы преобразовать его в реальный метод (сExpression.Compile
) или сделать другие вещи (например, пример LINQ to SQL) с ним. Обращение с лямбдами как с анонимными методами и деревьями выражений - это просто время компиляции.будет эффективно компилироваться в метод IL, который ничего не получает и возвращает 10.
будет преобразован в структуру данных, которая описывает выражение, которое не получает параметров и возвращает значение 10:
увеличенное изображение
Хотя они оба выглядят одинаково во время компиляции, то, что генерирует компилятор, совершенно разное .
источник
Expression
содержит метаинформацию об определенном делегате.Expression<Func<...>>
вместо простоFunc<...>
.(isAnExample) => { if(isAnExample) ok(); else expandAnswer(); }
такое выражение является ExpressionTree, ветки создаются для оператора If.Я добавляю ответ для новичков, потому что эти ответы казались мне над головой, пока я не понял, насколько это просто. Иногда вы ожидаете, что это сложно, что делает вас неспособным «обернуть голову вокруг этого».
Мне не нужно было понимать разницу, пока я не столкнулся с действительно раздражающей «ошибкой», пытаясь использовать LINQ-to-SQL в общем:
Это прекрасно работало, пока я не начал получать исключения 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
с другой стороны, позволяет заглянуть внутрь делегата и увидеть все, что он хочет сделать. Это позволяет вам перевести делегата во что угодно, например, в SQL-запрос.Func
не работал, потому что мой DbContext был слеп к содержанию лямбда-выражения. Из-за этого он не мог превратить лямбда-выражение в SQL; однако, он сделал следующее лучшее и повторил это условие по каждой строке в моей таблице.Изменить: изложив мое последнее предложение по просьбе Джона Питера:
IQueryable расширяет IEnumerable, поэтому методы IEnumerable, такие как
Where()
получение перегрузок, которые принимаютExpression
. Когда вы передаете этоExpression
, вы сохраняете IQueryable как результат, но когда вы передаете aFunc
, вы возвращаетесь к базовому IEnumerable, и в результате вы получаете IEnumerable. Другими словами, не замечая, что вы превратили свой набор данных в список для повторения, а не для запроса. Трудно заметить разницу, пока вы действительно не заглянете под капотом на подписи.источник
Чрезвычайно важным соображением при выборе Expression vs Func является то, что провайдеры IQueryable, такие как LINQ to Entities, могут «переваривать» то, что вы передаете в Expression, но игнорируют то, что вы передаете в Func. У меня есть два сообщения в блоге на эту тему:
Подробнее о Expression vs Func с Entity Framework и влюблении в LINQ - Часть 7. Выражения и функции (последний раздел)
источник
Я хотел бы добавить некоторые заметки о различиях между
Func<T>
иExpression<Func<T>>
:Func<T>
просто обычный MulticastDelegate старой школы;Expression<Func<T>>
является представлением лямбда-выражения в виде дерева выражений;Func<T>
;ExpressionVisitor
;Func<T>
;Expression<Func<T>>
.Есть статья, которая описывает детали с примерами кода:
LINQ: Func <T> против Expression <Func <T >> .
Надеюсь, это будет полезно.
источник
Об этом есть более философское объяснение из книги Кшиштофа Квалины ( Руководство по разработке структуры: условные обозначения , идиомы и шаблоны для многократно используемых библиотек .NET );
Изменить для версии без изображения:
источник
database.data.Where(i => i.Id > 0)
быть выполнены какSELECT FROM [data] WHERE [id] > 0
. Если вы просто передать в Func, вы положили шоры на драйвере , и все это может сделатьSELECT *
и то , как только он будет загружен все эти данные в память, итерацию по каждому и отфильтровывать все с ID> 0. оборачивать вашиFunc
вExpression
расширяющего драйвер для анализаFunc
и преобразования его в запрос Sql / MongoDb / other.Expression
но когда я в отпуске, это будетFunc/Action
;)LINQ - это канонический пример (например, общение с базой данных), но, по правде говоря, каждый раз, когда вы больше заботитесь о том, чтобы выразить, что делать, чем о том, что вы делаете. Например, я использую этот подход в стеке RPC protobuf-net (чтобы избежать генерации кода и т. Д.) - поэтому вы вызываете метод с помощью:
Это деконструирует дерево выражений для разрешения
SomeMethod
(и значение каждого аргумента), выполняет вызов RPC, обновляет любойref
/out
args и возвращает результат удаленного вызова. Это возможно только через дерево выражений. Я расскажу об этом подробнее здесь .Другой пример - когда вы вручную строите деревья выражений с целью компиляции в лямбду, как это делается с помощью универсального кода операторов .
источник
Вы бы использовали выражение, когда хотите рассматривать свою функцию как данные, а не как код. Вы можете сделать это, если хотите манипулировать кодом (как данными). В большинстве случаев, если вы не видите необходимости в выражениях, вам, вероятно, не нужно их использовать.
источник
Основная причина в том, что вы не хотите запускать код напрямую, а хотите его проверить. Это может быть по ряду причин:
источник
Expression
может быть также невозможно сериализовать как делегат, так как любое выражение может содержать вызов произвольной ссылки на делегат / метод. «Легко» относительно, конечно.Я пока не вижу ответов, в которых упоминается производительность. Передача
Func<>
сWhere()
илиCount()
плохо. Очень плохо Если вы используете,Func<>
тогда он вызываетIEnumerable
LINQ, а неIQueryable
, что означает, что целые таблицы извлекаются, а затем фильтруются.Expression<Func<>>
значительно быстрее, особенно если вы запрашиваете базу данных, которая живет на другом сервере.источник