Невозможно использовать метод расширения для делегата
Рассмотрим приведенный ниже пример. Я могу сделать вызов метода расширения для делегата, если сначала я определить переменную этого типа делегата. Но я не могу назвать этот метод расширения делегатом, который передан как аргумент. Я не понимаю, почему он работает в первый раз, но не работает второй. Что я делаю неправильно?
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
}
}
Ответы
Ответ 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