10

拡張メソッドに変換したいメソッドがあります

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

そしてそれを次のように呼び出します

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

したがって、次のように評価されますstr = "Bar"; // It gives the Member name and not its value

これでこれを拡張メソッドに変換しようとすると

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

そしてそれを次のように呼び出します

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

エラーは言うOperator '.' cannot be applied to operand of type 'lambda expression'

どこが間違っていますか?

4

3 に答える 3

8

どこが間違っていますか?

.コンパイラは、何が問題なのかを正確に伝えています。ラムダ式では使用できません。

ラムダ式には特定の型はありません。式ツリーに変換できるだけです。

メンバーアクセス式 (これはあなたがしようとしていることです) は、フォームでのみ使用できます

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

...そして、ラムダ式は一次式ではありません。

興味深いことに、この引数は無名メソッド式には当てはまりませんが、それでもメンバー アクセス式を使用することはできません。C#仕様のセクション7.6.4には、メンバーアクセス式がどのようにバインドされるかがリストされており、オプションの大部分は「Eが定義済みの型であるか、型として分類された一次式である場合」の下にあります(これは適用されません)匿名メソッドへ) または「E がプロパティ アクセス、変数、または値である場合、その型は T です」-しかし、匿名メソッドは匿名関数であり、セクション 7.15 によると、「匿名関数には値がありません。またはそれ自体を入力します。」

編集:式ツリーで拡張メソッドを引き続き使用できますが、ラムダ式で直接使用することはできません。したがって、これは機能します:

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

...しかし、明らかにそれほど役に立ちません。(mlorbetskeの回答に従ってキャストを使用して同上。)

于 2012-06-23T07:30:10.440 に答える
8

ここには実際には 2 つのことがあります。1 つ目は、指定された式ツリー() => new Foo().Barを受け入れるメソッドに渡すことですが、それ自体は ではありません。Expression<Func<T>>Expression<Func<T>>() => new Foo().BarExpression<Func<T>>

次に、拡張メソッドが任意のラムダ (指定したものなど) を受け入れるようにするには、任意の式ツリーに対応する型を使用する必要があります。しかし、引用符内の型の名前が通常表示されるメッセージに基づいて既に推測したよう... to operand of type 'lambda expression'に、ラムダ式は言語によって特別に扱われ、最初にキャストせずに、しようとしていることを行います。無理だよ。

拡張メソッド形式で拡張メソッドを呼び出す方法は次のようになります (Barタイプがの場合string)

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

それほど望ましいとは思えません。

于 2012-06-23T07:33:03.690 に答える
2

型付き式を取得するには、それを書き出す必要があります。他の人が言ったように、ラムダ式はデリゲートまたは式ツリーの2つのことを意味する可能性があるため、コンパイラがラムダ式から自動的にそれを推測する方法はありません。

(この回答から)のように、コンパイラに部分的に型を推測させることで、式を比較的単純にすることができます。

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

次のように呼び出します。

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

オプションは次のとおりです。

  1. 正確な式の型に逆方向にキャストし、その拡張メソッドを呼び出します

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

    醜く見える - 原因よりも悪い治療.

  2. 最初に式を変数に入れる

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

    わずかに良くなりましたが、些細な点ではまだ少しずれているように見えます。

  3. 上記のLambdaクラスを使用する

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

    さらに良い。タイピングが少し少ないだけです。

  4. あなたの最初のパターンは、読みやすさに関する限り、あなたが得ることができる最高のものだと思います

    ラムダ式に関連する C# の規則を考慮すると、それを改善できるとは思いません。そうは言っても、改善できることはほとんどないと思います。

    まず、機能を他のラムダ式タイプに拡張します。Func<T>すべてのケースを処理するには、2 つ以上の型が必要です。処理する必要がある式の種類を決定します。たとえば、Func<S, T>タイプがある場合(質問のように- Baron Foo)、見た目が良くなります。これを比較

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

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

    これらのオーバーロードは次のようになります。

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

    現在、これらはコメントで言及されている場合だけでなく、重複するシナリオも確実に使用できます。たとえば、 のインスタンスが既にある場合は、 のようにプロパティの名前に対してFoo2 番目のオーバーロード () オーバーロードを呼び出す方が簡単です。Func<T>Barmyclass.GetMemberName(() => foo.Bar)

    次に、GetMemberNameこれらすべてのオーバーロードに共通の機能を実装します。Expression<T>またはの拡張メソッドでLambdaExpression十分です。強く型付けされていないシナリオでも呼び出すことができるように、後者を好みます。この回答から、次のように書きます。

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

    最後に、GetMemberName非拡張的な方法で呼び出す場合は、適切な名前ではありません。expression.GetMemberName()より論理的に聞こえます。Member.NameFrom<int>(x => x.ToString())などMemberName.From<string>(x => x.Length)は、静的呼び出しのよりわかりやすい名前です。

    したがって、全体的なクラスは次のようになります。

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

    そして使用法:

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

    最も簡潔でクリーン。

  5. プロパティとフィールドの場合、ここに示すように、匿名クラスに変換し、リフレクションを使用してメンバー名を読み取ることができます。

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

    のように呼びます

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

    高速ですが、リファクタリングにあまり適していないなどの特定の癖があり、メンバーとしてのメソッドの場合は役に立ちません。スレッドを参照してください..

中でも私は4が好きです。

于 2013-04-27T01:17:34.997 に答える