Подтвердить что ты не робот

Почему аргументы выражения лямбда неоднозначны между 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 действует как явное преобразование из дерева выражений в делегат. Дерево выражений содержит больше информации, и когда вы компилируете делегата, вы теряете эту информацию. В другом направлении нет конверсии.

Есть ли какие-либо причины не предпочитать дерево выражений?

4b9b3361

Ответ 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> заботится только о параметрах и обратном типе и не имеет таких ограничений.