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

Невозможно использовать метод расширения для делегата

Рассмотрим приведенный ниже пример. Я могу сделать вызов метода расширения для делегата, если сначала я определить переменную этого типа делегата. Но я не могу назвать этот метод расширения делегатом, который передан как аргумент. Я не понимаю, почему он работает в первый раз, но не работает второй. Что я делаю неправильно?

public static class Extender
{
    public static Func<String, String> Compose(this Func<String, String> outer, Func<String, String> inner)
    {
        return input => outer(inner(input));
    }
}
public class Demo
{
    public void WillingToTakeStringToStringDelegate(Func<String, String> map)
    {
        // blah
    }
    public void RunMe()
    {
        Func<String, String> outer = x => "(outer: " + x + ")";

        // this works:
        var composition = outer.Compose(x => "(inner: " + x + ")");
        Trace.Write(composition("!"));  // ---> (outer: (inner: !))

        // this doesn't work:
        this.WillingToTakeStringToStringDelegate(
            (x => "(outer: " + x + ")").Compose(y => "(inner: " + y + ")")
        );
    }
}

UPDATE

для @philologon

Пока вы не возражаете, чтобы назначить свои лямбды переменным, тогда да, вы можете использовать этот метод для создания частичных приложений функций (currying), таких как boss:

public static class CurryingHelper
{
    public static Func<X> Apply<A, X>(this Func<A, X> fun, A a)
    {
        return () => fun(a);
    }
    public static Func<B, X> Apply<A, B, X>(this Func<A, B, X> fun, A a)
    {
        return b => fun(a, b);
    }
    public static Func<B, C, X> Apply<A, B, C, X>(this Func<A, B, C, X> fun, A a)
    {
        return (b, c) => fun(a, b, c);
    }
    public static Func<B, C, D, X> Apply<A, B, C, D, X>(this Func<A, B, C, D, X> fun, A a)
    {
        return (b, c, d) => fun(a, b, c, d);
    }

    // etc... 
}

public class Demo
{
    public void RunMe()
    {
        Func<Int32, Int32, Int32, Int32> func = (a, b, c) => a - b + c;
        var funcA1 = func.Apply(1);
        Trace.Write(funcA1(2, 3));               // --> 2
        Trace.Write(funcA1.Apply(2).Apply(3)()); // --> 2
    }
}
4b9b3361

Ответ 1

В этой концепции нет ничего плохого, только некоторые технические проблемы с исполнением.

Дело в том, что x => "(outer: " + x + ")" не является делегатом без контекста: это лямбда-выражение, которое может либо соответствовать делегату (некоторого типа), либо даже дереву выражений. Поэтому тип должен быть явно или неявно объявлен, например.

// this works:
this.WillingToTakeStringToStringDelegate(
    ((Func<string, string>)(x => "(outer: " + x + ")")).Compose(...)
);

Это то же самое, почему вы не можете назначать лямбда-функции неявно типизированным переменным, например.

var f1 = (string s) => "Hello " + s;                   // does not work
Func<string, string> f2 = (string s) => "Hello " + s;  // works fine

Ответ 2

Лямбда-выражения в С# не имеют типов сами по себе. Например, вы можете назначить лямбда-выражение x => x != 0 на Predicate<int>, Func<int, bool>, Func<long, bool> или YourCustomDelegate.

Итак, всякий раз, когда вы используете выражение лямбда, вам нужно дать подсказку компилятору, какой тип делегата следует использовать.

Примеры:

  • Это работает. Подсказка - это тип переменной outer.

    Func<String, String> outer = x => "(outer: " + x + ")";
    
  • Это работает. Подсказка - это тип параметра inner метода Compose.

    var composition = outer.Compose(x => "(inner: " + x + ")");
    
  • Это не работает, потому что для (x => "(outer: " + x + ")") нет подсказок:

    this.WillingToTakeStringToStringDelegate(
        (x => "(outer: " + x + ")").Compose(y => "(inner: " + y + ")")
    );
    

Ответ 3

Другие ответы правильные; Я просто хотел заметить, что команда дизайнеров сознательно выбрала, что методы расширения не работают ни с одним выражением, у которого не было никакого типа, поэтому нет методов расширения для lambdas, анонимных методов, групп нулей или методов или любого динамического выражения.

На самом деле он идет дальше этого; выражение слева от точки должно быть преобразовано в первый параметр с помощью идентичности, неявной ссылки или преобразования бокса. Итак, другими словами:

enum E { }
static class Ext
{
    public static E X(this E e) { return e; }
}

// Legal
E e1 = 0;
// Legal
E e2 = e1.X();
// No way José.
E e3 = 0.X();

Это не идентичность, ссылка или преобразование бокса.

Принципы языкового дизайна на работе здесь, во-первых, не неприятные сюрпризы. Методы расширения являются поздним дополнением к языку, и команда разработчиков хотела быть очень осторожным в том, чтобы не добавлять ситуации, когда они станут применимыми удивительными способами.

И, во-вторых, в большинстве случаев причины С# связаны с типами выражений изнутри наружу. То есть, когда мы видим x = y, мы анализируем типы x и y независимо друг от друга, а затем решаем, является ли назначение законным. Но для беспримерных выражений, которые инвертируются. Для x = (y)=>{whatever} мы анализируем тип x, а затем используем это, чтобы решить, является ли (y)=>{whatever} законной правой стороной, и если да, то какой тип это и какой тип находится внутри whatever. Эта инверсия нормального порядка вещей приводит к очень сложному коду в компиляторе, и никто не захотел добавить еще один случай, когда нам нужно было бы делать рассуждения изнутри.

Наконец, поскольку, по-видимому, вас интересует каррирование, это может вас заинтересовать.

http://blogs.msdn.com/b/ericlippert/archive/2009/06/25/mmm-curry.aspx