Ответ 1
Ваш второй делегат не является переписыванием первого в анонимном делетете (а не лямбда) формате. Посмотрите на свои условия.
Во-первых:
x.ID == packageId || x.Parent.ID == packageId || x.Parent.Parent.ID == packageId
Во-вторых:
(x.ID == packageId) || (x.Parent != null && x.Parent.ID == packageId) ||
(x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId)
При вызове лямбда будет выведено исключение для любого x
, где идентификатор не совпадает, и либо родительский объект равен null, либо не соответствует, а grandparent - null. Скопируйте нулевые проверки в лямбда и он должен работать правильно.
Изменить после комментария к вопросу
Если ваш исходный объект не является List<T>
, тогда мы не можем узнать, что такое тип возврата FindAll()
, и реализует ли он интерфейс IQueryable
. Если да, то это, вероятно, объясняет несоответствие. Поскольку lambdas можно преобразовать во время компиляции в Expression<Func<T>>
, но анонимные делегаты не могут, тогда вы можете использовать реализацию IQueryable
при использовании лямбда-версии, но LINQ-to-Objects при использовании анонимной версии делегата.
Это также объясняет, почему ваша лямбда не вызывает NullReferenceException
. Если вы передадите это выражение лямбда тому, что реализует IEnumerable<T>
, но не IQueryable<T>
, оценка времени выполнения лямбда (которая ничем не отличается от других методов, анонимная или нет) будет бросать NullReferenceException
в первый раз, когда она столкнулась объект, в котором ID
не был равен цели, а родительский или дед-барабан был нулевым.
Добавлен 3/16/2011 8:29 утра EDT
Рассмотрим следующий простой пример:
IQueryable<MyObject> source = ...; // some object that implements IQueryable<MyObject>
var anonymousMethod = source.Where(delegate(MyObject o) { return o.Name == "Adam"; });
var expressionLambda = source.Where(o => o.Name == "Adam");
Эти два метода дают совершенно разные результаты.
Первый запрос - это простая версия. Анонимный метод приводит к тому, что делегат затем передается методу расширения IEnumerable<MyObject>.Where
, где все содержимое source
будет проверено (вручную в памяти с использованием обычного скомпилированного кода) против вашего делегата. Другими словами, если вы знакомы с итераторными блоками в С#, то что-то вроде этого:
public IEnumerable<MyObject> MyWhere(IEnumerable<MyObject> dataSource, Func<MyObject, bool> predicate)
{
foreach(MyObject item in dataSource)
{
if(predicate(item)) yield return item;
}
}
Главное, что вы на самом деле выполняете фильтрацию в памяти на стороне клиента. Например, если ваш источник был SQL ORM, в запросе не было бы предложения WHERE
; весь результирующий набор будет возвращен клиенту и отфильтрован там.
Второй запрос, который использует лямбда-выражение, преобразуется в Expression<Func<MyObject, bool>>
и использует метод расширения IQueryable<MyObject>.Where()
. Это приводит к тому, что объект также напечатан как IQueryable<MyObject>
. Все это работает, передавая это выражение базовому провайдеру. Вот почему вы не получаете NullReferenceException
. Это полностью зависит от поставщика запроса, как преобразовать выражение (которое вместо того, чтобы быть фактически скомпилированной функцией, которую он может просто вызвать, представляет собой представление логики выражения с использованием объектов) во что-то, что он может использовать.
Простым способом увидеть различие (или, по крайней мере, это есть) было бы различие, заключалось бы в вызове AsEnumerable()
перед вашим вызовом WHERE
в лямбда-версии. Это заставит ваш код использовать LINQ-to-Objects (это означает, что он работает на IEnumerable<T>
, как анонимная версия делегата, а не IQueryable<T>
, как в настоящее время версия лямбда), и вы получите исключения, как ожидалось.
TL; версия DR
Долгое и короткое из того, что ваше лямбда-выражение переводится в какой-то запрос к вашему источнику данных, тогда как версия анонимного метода оценивает весь источник данных в памяти. Все, что делает перевод вашей лямбды в запрос, не представляет логику, которую вы ожидаете, поэтому она не дает ожидаемых результатов.