С#: неявный оператор и методы расширения

Я пытаюсь создать класс PredicateBuilder<T>, который обертывает Expression<Func<T, bool>> и предоставляет некоторые методы, позволяющие легко создать выражение с различными методами And и Or. Я подумал, что было бы здорово, если бы я мог использовать этот PredicateBuilder<T> как Expression<Func<T, bool>> напрямую, и подумал, что это может быть сделано с помощью метода метода implicit operator.

Урезанная версия класса выглядит следующим образом:

class PredicateBuilder<T>
{
    public Expression<Func<T, bool>> Predicate { get; protected set; }

    public PredicateBuilder(bool initialPredicate)
    {
        Predicate = initialPredicate 
            ? (Expression<Func<T, bool>>) (x => true) 
            : x => false;
    }

    public static implicit operator Expression<Func<T, bool>>(
        PredicateBuilder<T> expressionBuilder)
    {
        return expressionBuilder.Predicate;
    }
}

Затем, как и тест, у меня есть этот метод расширения в статическом классе:

public static void PrintExpression<T>(this Expression<Func<T, bool>> expression)
{
    Console.WriteLine(expression);
}

В моей голове я должен был бы сделать это:

var p = new PredicateBuilder<int>(true);

p.PrintExpression();
PredicateExtensions.PrintExpression(p);

Однако никто из них не работает. Для первого метода расширения не найдено. А во-вторых, говорится, что

Аргументы типа для метода "ExtravagantExpressions.PredicateHelper.PrintExpression(System.Linq.Expressionions.Expression > )" не могут быть выведены из использования. Попробуйте явно указать аргументы типа.

Итак, я попробовал следующее:

PredicateExtensions.PrintExpression<int>(p);

Кроме того, это работает, конечно:

((Expression<Func<int, bool>>) p).PrintExpression();

Но да... почему другие не работают? Я что-то неправильно понял о том, как это работает implicit operator?

Ответ 1

Это не относится к методам расширения. С# не будет неявным образом передавать объект другому типу, если не будет указаний относительно целевого типа. Предположим следующее:

class A {
    public static implicit operator B(A obj) { ... }
    public static implicit operator C(A obj) { ... }
}

class B {
    public void Foo() { ... }
}

class C {
    public void Foo() { ... }
}

Какой метод вы ожидаете вызвать в следующем выражении?

new A().Foo(); // B.Foo? C.Foo? 

Ответ 2

Нет, вы этого не сделали, но выведение типа компилятора С# недостаточно мощно, чтобы понять ваш код, и, в частности, он не смотрит на неявные операторы. Вам придется придерживаться Expression<Func<T,bool>> - почему бы не использовать методы расширения, такие как Or, And непосредственно в выражениях?

Ответ 3

Как говорит Антон, если вы поместите методы расширения непосредственно на Expression<Func<...>>, это, вероятно, сработает.

Больше объяснений... ничего особенно умного, но идея в том, что у вас нет класса PredicateBuilder, который вы создаете экземпляры. Вместо этого у вас просто чисто статические строительные блоки:

public static class Predicates
{
    public static Expression<Func<T, bool>> True<T>()
    {
        return x => true;
    }

    public static Expression<Func<T, bool>> False<T>()
    {
        return x => false;
    }

    public static Expression<Func<T, bool>> And<T>(
        this Expression<Func<T, bool>> left,
        Expression<Func<T, bool>> right)
    {
        return ... // returns equivalent of (left && right)
    }
}

Эти две функции True и False играют роль вашего конструктора PredicateBuilder(bool), и вы предположительно имели бы похожие для примитивных сравнений и т.д., а затем такие операторы, как And, могли бы подключить два выражения вместе.

Однако, вы теряете возможность использовать символы оператора, которые вы могли бы использовать с вашим объектом-оболочкой, и вместо этого вы должны использовать имена методов. Я играл с такими же подходами, и я всегда возвращался к тому, что хочу иметь возможность определять операторы расширения. Команда С#, по-видимому, рассматривала их для 3.0 (наряду с свойствами расширения), но они были более низкими приоритетами, поскольку они не играли роли в общих целях Linq.