2012-06-23 9 views
8

Ich habe eine Methode, die ichFehlermeldung "Operator '." kann nicht auf Operanden vom Typ 'Lambda-Ausdruck' angewendet werden, wenn eine Methode in die Erweiterungsmethode konvertiert wird?

public static string GetMemberName<T>(Expression<Func<T>> item) 
{ 
    return ((MemberExpression)item.Body).Member.Name; 
} 

und es wie

string str = myclass.GetMemberName(() => new Foo().Bar); 

so wertet es str = "Bar"; // It gives the Member name and not its value

Jetzt anrufen, wenn ich auf Erweiterungsmethode konvertieren wollen versuchen, dies zu Erweiterung konvertieren Methode durch diese

public static string GetMemberName<T>(this Expression<Func<T>> item) 
{ 
    return ((MemberExpression)item.Body).Member.Name; 
} 

und nennen Sie es wie

string str = (() => new Foo().Bar).GetMemberName(); 

Fehler sagt Operator '.' cannot be applied to operand of type 'lambda expression'

Wo ich falsch bin?

Antwort

7

Es gibt wirklich zwei Dinge hier zunächst () => new Foo().Bar in die Methode übergeben, die Expression<Func<T>> behandelt den angegebenen Ausdruck Baum als Expression<Func<T>> akzeptiert, aber () => new Foo().Bar ist kein Expression<Func<T>> auf seine eigene .

Zweitens, um Ihre Erweiterungsmethode zu erhalten, um Lambda zu akzeptieren (wie Sie liefern), müssten Sie den Typ verwenden, der jeder Ausdrucksbaumstruktur entspricht. Aber wie Sie vielleicht bereits anhand der Nachricht ... to operand of type 'lambda expression' erraten haben, wo Sie normalerweise den Namen des Typs in den Anführungszeichen sehen würden, werden diese Lambda-Ausdrücke speziell von der Sprache behandelt, was Sie versuchen zu tun, ohne zuerst zu werfen , unmöglich.

Die Art und Weise Ihre Erweiterungsmethode in Erweiterungsmethode Form aufzurufen wäre (in dem Fall, dass Bar vom Typ string ist)

((Expression<Func<string>>)(() => new Foo().Bar)).GetMemberName()` 

, die wie es scheint nicht, wäre alles, was wünschenswert zu sein.

+0

Arbeitete für mich .. –

+0

Ich bin mir nicht sicher, ich folge Ihrem Kommentar, es funktioniert für Sie, ohne zuerst auf den spezifischen Ausdruck > 'oder diese Art der Berufung ist zufriedenstellend? – mlorbetske

+0

Ihre Antwort funktionierte für mich '((Ausdruck >) (() => neu Foo(). Bar)). GetMemberName()' –

6

Wo liege ich falsch?

Der Compiler sagt Ihnen genau, was falsch ist - Sie können . nicht für einen Lambda-Ausdruck verwenden.

Der Lambda-Ausdruck hat keinen bestimmten Typ - es ist nur konvertierbar in den Ausdrucksbaum.

A Memberzugriff Ausdruck (das ist, was Sie versuchen zu tun) ist nur in den Formen

primary-expression . identifier type-argument-list(opt) 
predefined-type . identifier type-argument-list(opt) 
qualified-alias-member . identifier type-argument-list(opt) 

... und ein Lambda-Ausdruck ist kein einfacher Ausdruck.

Interessanterweise dieses Argument nicht Halt für einen anonymen Methode Ausdruck, aber für Sie immer noch nicht einen Mitglied Zugriffsausdruck auf das verwenden, auch nicht. Abschnitt 7.6.4 der C# -Spezifikation listet auf, wie ein Mitgliedszugriffsausdruck gebunden ist, und der Hauptteil der Optionen ist entweder "Wenn E ein vordefinierter Typ oder primärer Ausdruck ist, der als Typ klassifiziert wurde" (trifft nicht auf anonyme Methoden zu) oder "Wenn E ein Eigenschaftenzugriff, eine Variable oder ein Wert ist, dessen Typ T ist" - aber eine anonyme Methode ist eine anonyme Funktion und gemäß Abschnitt 7.15: "Eine anonyme Funktion tut dies keinen Wert haben oder selbst eingeben ".

BEARBEITEN: Sie können immer noch Erweiterungsmethoden auf Ausdrucksbäume verwenden, Sie können sie nicht direkt auf Lambda-Ausdrücke verwenden. So wird diese Arbeit:

Expression<Func<int>> expr =() => new Foo().Bar; 
string name = expr.GetMemberName(); 

... aber es ist natürlich nicht so nützlich. (Ditto mit einer Besetzung als pro mlorbetske Antwort.)

+0

Danke, aber bedeutet das, dass dieses Verfahren nicht zu EM umgewandelt werden? –

+0

@NikhilAgrawal: Nun, Sie können es tun - aber nicht in einem Schritt. –

+0

oder als @mlorbetske antwortet '((Ausdruck >) (() => neue Foo(). Bar)). GetMemberName();' –

2

Um einen getippten Ausdruck zu erhalten, müssen Sie ihn ausschreiben. Wie andere gesagt haben, gibt es keinen Weg, wie der Compiler automatisch von einem Lambda-Ausdruck auf einen solchen schließen kann, da ein Lambda-Ausdruck zwei Dinge bedeuten kann - entweder einen Delegierten- oder einen Ausdrucksbaum.

Sie können den Ausdruck bekommen relativ einfacher, indem man der Compiler den Typ für Sie teilweise schließen, wie (from this answer):

public sealed class Lambda 
{ 
    public static Func<T> Func<T>(Func<T> func) 
    { 
     return func; 
    } 

    public static Expression<Func<T>> Expression<T>(Expression<Func<T>> expression) 
    { 
     return expression; 
    } 
} 

public sealed class Lambda<S> 
{ 
    public static Func<S, T> Func<T>(Func<S, T> func) 
    { 
     return func; 
    } 

    public static Expression<Func<S, T>> Expression<T>(Expression<Func<S, T>> expression) 
    { 
     return expression; 
    } 
} 

//etc, to cover more cases 

nennen es mag:

var expr1 = Lambda.Expression(() => new Foo().Bar); 
var expr2 = Lambda<string>.Expression(x => x.Length); //etc 

Ihre Optionen sind:

  1. rückwärts Gegossenes Ausdrucksart, genaue und rufen Erweiterungsmethode dann auf sie

    var name = ((Expression<Func<BarType>>)(() => new Foo().Bar)).GetMemberName(); 
    

    hässlich aussieht - heilen schlechter als Ursache.

  2. Holen Sie sich den Ausdruck zuerst in eine Variable

    Expression<Func<BarType>> expr =() => new Foo().Bar; 
    var name = expr.GetMemberName(); 
    

    Etwas besser, sieht aber immer noch wenig für eine triviale Sache ab.

  3. die Lambda Klassen über

    geschrieben
    var name = Lambda.Expression(() => new Foo().Bar).GetMemberName(); 
    

    Noch besser ist. Es ist nur ein bisschen weniger tippen.

  4. Ihr erstes Muster, das ich denke, das Beste ist, Sie so weit als Lesbarkeit

    geht Ich glaube nicht, dass Sie auf, dass unter Berücksichtigung C# Regeln im Zusammenhang mit Lambda-Ausdrücke verbessern. Das heißt, ich denke, dass einige Verbesserungen vorgenommen werden können.

    Erweitern Sie zuerst die Funktionalität auf andere Lambda-Ausdruckstypen. Sie würden mehr als Func<T> Typen benötigen, um alle Fälle zu behandeln. Entscheiden Sie, welche Ausdruckstypen Sie behandeln müssen. Zum Beispiel, wenn Sie eine Func<S, T> Art haben (wie in Ihrer Frage - Bar auf Foo), sieht es besser aus.Vergleichen Sie diese

    myclass.GetMemberName(() => new Foo().Bar); 
    

    mit

    myclass.GetMemberName<Foo>(x => x.Bar); 
    

    ich diese Überlastungen sagen würde tun würde:

    //for static methods which return void 
    public static string GetMemberName(Expression<Action> expr); 
    
    //for static methods which return non-void and properties and fields 
    public static string GetMemberName<T>(Expression<Func<T>> expr); 
    
    //for instance methods which return void 
    public static string GetMemberName<T>(Expression<Action<T>> expr); 
    
    //for instance methods which return non-void and properties and fields 
    public static string GetMemberName<S, T>(Expression<Func<S, T>> expr); 
    

    Jetzt können diese verwendet werden, nicht nur in den in den Kommentaren genannten Fällen sicher gibt es überlappende Szenarien. Zum Beispiel, wenn Sie bereits eine Instanz von Foo haben, dann ist es einfacher, die zweite Überladung (Func<T>) Überladung für den Namen der Eigenschaft Bar, wie myclass.GetMemberName(() => foo.Bar).

    Zweitens implementieren Sie eine GetMemberName Funktionalität für alle diese Überlastungen. Eine Erweiterungsmethode unter Expression<T> oder LambdaExpression würde ausreichen. Ich bevorzuge Letzteres, so dass Sie es sogar in nicht stark typisierten Szenarien nennen können. Ich würde es so schreiben, from this answer:

    public static string GetMemberName(this LambdaExpression memberSelector) 
    { 
        Func<Expression, string> nameSelector = null; 
        nameSelector = e => //or move the entire thing to a separate recursive method 
        { 
         switch (e.NodeType) 
         { 
          case ExpressionType.Parameter: 
           return ((ParameterExpression)e).Name; 
          case ExpressionType.MemberAccess: 
           return ((MemberExpression)e).Member.Name; 
          case ExpressionType.Call: 
           return ((MethodCallExpression)e).Method.Name; 
          case ExpressionType.Convert: 
          case ExpressionType.ConvertChecked: 
           return nameSelector(((UnaryExpression)e).Operand); 
          case ExpressionType.Invoke: 
           return nameSelector(((InvocationExpression)e).Expression); 
          case ExpressionType.ArrayLength: 
           return "Length"; 
          default: 
           throw new Exception("not a proper member selector"); 
         } 
        }; 
    
        return nameSelector(memberSelector.Body); 
    } 
    

    Schließlich GetMemberName ist kein guter Name, wenn Sie es nennen Nichtverlängerung Weg sind. expression.GetMemberName() klingt logischer. Member.NameFrom<int>(x => x.ToString()) oder MemberName.From<string>(x => x.Length) usw. sind aussagekräftigere Namen für statische Aufrufe.

    Also insgesamt könnte die Klasse wie folgt aussehen:

    public static class Member 
    { 
        public static string NameFrom(Expression<Action> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    
        public static string NameFrom<T>(Expression<Func<T>> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    
        public static string NameFrom<T>(Expression<Action<T>> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    
        public static string NameFrom<T>(Expression<Func<T, object>> expr) 
        { 
         return expr.GetMemberName(); 
        } 
    } 
    

    Und Nutzung:

    var name1 = Member.NameFrom(() => Console.WriteLine()); 
    var name2 = Member.NameFrom(() => Environment.ExitCode); 
    var name3 = Member.NameFrom<Control>(x => x.Invoke(null)); 
    var name4 = Member.NameFrom<string>(x => x.Length); 
    

    prägnanteste und sauber.

  5. Für Eigenschaften und Felder, kann es zu einer anonymen Klasse umgewandelt werden und dann rufen

    public static string GetMemberName<T>(T item) where T : class 
    { 
        if (item == null) 
         return null; 
    
        return typeof(T).GetProperties()[0].Name; 
    } 
    

    as shown here.

    mit Reflexion kann der Elementname gelesen werden, wie

    var name = GetMemberName(new { new Foo().Bar }); 
    

    es schneller ist, aber hat gewisse Macken, wie nicht sehr Refactor freundlich, und hilft nicht im Falle von Methoden als Mitglieder. Siehe das Gewinde ..

Von allen ziehe ich 4.

Verwandte Themen