Почему аргументы выражения лямбда неоднозначны между Func и Expression <Func>?
Предположим, что у меня есть класс:
class MyClass {
public int MyMethod(Func<int, int> f) { return 0; }
public int MyMethod(Expression<Func<int, int>> f) { return 1; }
}
Когда я пытаюсь вызвать метод с выражением лямбда, я получаю ошибку компиляции, заявляя, что вызов неоднозначен между двумя перегрузками:
var myClass = new MyClass();
myClass.MyMethod(x => 1 + x); // Error!
в то время как, конечно, вызов с явным типом работает нормально:
myClass.MyMethod((Func<int, int>)(x => 1 + x)); // OK, returns 0
myClass.MyMethod((Expression<Func<int, int>>)(x => 1 + x)); // OK, returns 1
Дерево выражений содержит больше информации (фактический код), и я могу использовать эту информацию, когда она доступна. Но я также хочу, чтобы мой код работал с делегатами. К сожалению, эта двусмысленность делает это, поэтому я должен найти другой способ различать два вызова, которые испортили иначе чистый API.
Спецификация С# ничего не говорит об этой конкретной ситуации, поэтому в этом смысле поведение действительно соответствует спецификации.
Однако есть аргумент, который следует сделать, чтобы дерево выражений было предпочтительнее делегата. Метод Compile
действует как явное преобразование из дерева выражений в делегат. Дерево выражений содержит больше информации, и когда вы компилируете делегата, вы теряете эту информацию. В другом направлении нет конверсии.
Есть ли какие-либо причины не предпочитать дерево выражений?
Ответы
Ответ 1
Отвечая на вопрос о названии, они неоднозначны, потому что система типов не имеет понятия "лямбда-выражения". Это функция компилятора, которая может быть преобразована в делегат или дерево выражений, поэтому вам нужно быть явным, к какому типу вы хотите его преобразовать. В большинстве случаев цель также автоматически выводится компилятором из-за контекста, в котором используется выражение лямбда. Например, использование lambdas в методах расширения IEnumerable
по сравнению с использованием lambdas в методах расширения IQueryable
.
Теперь, чтобы ответить на вопрос о том, почему не всегда предпочитаете дерево выражений, у вас есть аргумент производительности, который MagnatLU уже заявил. Если вы принимаете Expression
, а затем вызываете Compile
, чтобы выполнить его, то он будет всегда медленнее по сравнению с принятием делегата.
Существует также семантическая разница между ними, делегат - это просто способ выполнить некоторый код, в то время как дерево выражений является описанием фактического кода.
Если бы я был вами, я бы предпочел изменить имя метода, принимающего выражение, на то, что четко отражает то, что он делает дополнительно, на основе делегата.
Ответ 2
Как компилятор когда-либо узнает, нужно ли выбирать дерево выражений или делегат? Это ваше решение, а не компилятор.
Все методы linq также предоставляют делегат и выражения, но они являются расширениями в виде целевого типа.
Enumerable.Where<T>(this IEnumerable<T> en , Func<T,bool> filter)
Queryable.Where<T>(this IQueryable<T> en , Expression<Func<T,bool>> filter)
Таким образом, на основе целевого типа компилятор делает выбор. Компилятор - это программа, на самом деле это машина и она свободна от контекста, она не может принять решение, как человек, в том, что может быть лучше, оно следует только правилам, и эти правила должны быть однозначными.
Ответ 3
Построение и компиляция дерева выражений требует времени и может оказать значительное давление на GC. Вы не должны создавать и компилировать выражение во время выполнения, если вам действительно не нужно.
Изменить: Также помните, что не каждое выражение или метод может быть выражено как Expression<E>
. Func<U, V>
заботится только о параметрах и обратном типе и не имеет таких ограничений.