Frage Eine Erweiterungsmethode kann nicht für einen Delegaten verwendet werden


Betrachten Sie das Beispiel unten. Ich bin in der Lage, eine Erweiterungsmethode für einen Delegaten aufzurufen, wenn ich das erste Mal bin Definieren Sie eine Variable von diesem Delegattyp. Aber ich kann diese Erweiterungsmethode nicht für einen Delegaten aufrufen, der das ist als Argument übergeben. Ich verstehe nicht, warum es beim ersten Mal funktioniert, aber nicht beim zweiten. Was mache ich falsch?

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 + ")")
        );
    }
}

AKTUALISIEREN

für @philologon

Solange es dir nichts ausmacht, deine lambdas Variablen zuzuweisen, dann kannst du mit dieser Methode partielle Anwendungen von Funktionen (currying) wie einen Chef erstellen:

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
    }
}

14
2017-10-22 18:00


Ursprung


Antworten:


Es ist nichts falsch mit der Konzeption, nur einige technische Probleme mit der Ausführung.

Der Punkt ist, dass x => "(outer: " + x + ")" ist nicht Ein Delegat ohne Kontext: Es ist ein Lambda - Ausdruck, der entweder einem Delegaten (von etwas Typ) oder sogar zu einem Ausdrucksbaum. Daher muss der Typ explizit oder implizit deklariert werden, z.

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

Aus genau diesem Grund können Sie implizit typisierten Variablen keine Lambda-Funktionen zuweisen, z.

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

12
2017-10-22 18:11



Lambda-Ausdrücke in C # haben keine eigenen Typen. Zum Beispiel können Sie den Lambda-Ausdruck zuweisen x => x != 0 zu Predicate<int>, Func<int, bool>, Func<long, bool> oder YourCustomDelegate.

Wenn Sie also einen Lambda-Ausdruck verwenden, müssen Sie dem Compiler einen Hinweis geben, welcher Delegattyp verwendet werden soll.

Sch Da sch sch sch sch sch sch sch Da sch sch sch sch sch sch sch sch sch dieser

  • Das funktioniert. Der Hinweis ist der Typ der Variablen outer.

    Func<String, String> outer = x => "(outer: " + x + ")";
    
  • Das funktioniert. Der Hinweis ist der Typ des Parameters inner des Compose Methode.

    var composition = outer.Compose(x => "(inner: " + x + ")");
    
  • Dies funktioniert nicht, da kein Hinweis angegeben ist (x => "(outer: " + x + ")"):

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

10
2017-10-22 18:11



Die anderen Antworten sind korrekt; Ich wollte nur das Designteam erwähnen bewusst wähle diese Erweiterungsmethoden nicht Arbeitet an jedem Ausdruck, der keinen Typ hat - also keine Erweiterungsmethoden für Lambdas, anonyme Methoden, Null- oder Methodengruppen oder irgendeinen dynamischen Ausdruck.

Tatsächlich geht es weiter; Der Ausdruck auf der linken Seite des Punktes muss durch einen in den ersten Parameter konvertierbar sein Identität, implizite Referenz oder Boxen Umwandlung. Also mit anderen Worten:

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();

Das ist keine Identität, Referenz oder Box-Konvertierung.

Die Prinzipien des Sprachdesigns, die hier zum Einsatz kommen, sind erstens keine bösen Überraschungen. Erweiterungsmethoden sind eine späte Ergänzung der Sprache, und das Designteam wollte sehr vorsichtig sein, keine Situationen hinzuzufügen, in denen sie auf überraschende Weise anwendbar wären.

Und zweitens, in den meisten Fällen C # Gründe für die Art der Ausdrücke von innen nach außen. Das heißt, wenn wir sehen x = y Wir analysieren die Typen von x und y unabhängig voneinander und entscheiden dann, ob die Zuweisung legal ist. Aber für typlose Ausdrücke ist das umgekehrt. Zum x = (y)=>{whatever} Wir analysieren den Typ von x und bestimmen dann, ob (y)=>{whatever} ist eine legale rechte Seite, und wenn ja, welcher Typ ist es und was gibt alles drin whateverist. Diese Umkehrung der normalen Ordnung der Dinge führt zu einigen sehr komplizierter Code im Compiler und niemand war begierig darauf, noch einen weiteren Fall hinzuzufügen, in dem wir eine Inside-Out-Schlussfolgerung machen müssten.

Schließlich, da Sie sich offensichtlich für das Curry interessieren, könnte dies für Sie von Interesse sein.

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


7
2017-10-22 21:24