ラムダ式からプロパティ名を取得する


513

ラムダ式を介して渡されたときにプロパティ名を取得するより良い方法はありますか?これが私が現在持っているものです。

例えば。

GetSortingInfo<User>(u => u.UserId);

プロパティが文字列の場合にのみ、それをメンバー式としてキャストすることで機能しました。すべてのプロパティが文字列であるとは限らないため、オブジェクトを使用する必要がありましたが、それらはそれらの単項式を返します。

public static RouteValueDictionary GetInfo<T>(this HtmlHelper html, 
    Expression<Func<T, object>> action) where T : class
{
    var expression = GetMemberInfo(action);
    string name = expression.Member.Name;

    return GetInfo(html, name);
}

private static MemberExpression GetMemberInfo(Expression method)
{
    LambdaExpression lambda = method as LambdaExpression;
    if (lambda == null)
        throw new ArgumentNullException("method");

    MemberExpression memberExpr = null;

    if (lambda.Body.NodeType == ExpressionType.Convert)
    {
        memberExpr = 
            ((UnaryExpression)lambda.Body).Operand as MemberExpression;
    }
    else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
    {
        memberExpr = lambda.Body as MemberExpression;
    }

    if (memberExpr == null)
        throw new ArgumentException("method");

    return memberExpr;
}

より良いコードのように良いですか?私はそうは思いません。型チェックは式全体にまで及ぶため、実行時に行うチェックが本当に必要です。:(
MichaelGG 2009年

ええ...ちょっとハックに感じたので、もっと良い方法があるのではないかと思っていました。しかし、それならそれはクールです。ありがとう。
Schotime 2009年

コメントを更新しました。しかし、ラムダを使用して文字列を取得することで、動的LINQを使用して逆方向に物事を行うように感じる...ラムダを使用する場合は、ラムダを使用する;-p 1つのステップでクエリ全体を実行する必要はありません-あなたは、「定期的な/ラムダ」のOrderByを使用することができ、「動的LINQ /文字列」どこなど
マルクGravell


4
全員への注意:MemberExpressionここにリストされているアプローチは、実際の名前を取得するのではなく、メンバーの名前を取得するためにのみ使用してください。返される値が特定の「派生:基本」シナリオで反映された型であることが保証されていないMemberInfoためMemberInfoです。lambda-expression-not-returning-expected-memberinfoを参照してください。一度私をつまずかせた。受け入れられた答えもこれに苦しんでいます。
nawfal 2013

回答:


350

私は最近、タイプセーフなOnPropertyChangedメソッドを作成するために非常によく似たことを行いました。

式のPropertyInfoオブジェクトを返すメソッドは次のとおりです。式がプロパティでない場合は、例外がスローされます。

public PropertyInfo GetPropertyInfo<TSource, TProperty>(
    TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda)
{
    Type type = typeof(TSource);

    MemberExpression member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));

    PropertyInfo propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));

    if (type != propInfo.ReflectedType &&
        !type.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

このsourceパラメーターは、コンパイラーがメソッド呼び出しで型推論を行えるようにするために使用されます。次のことができます

var propertyInfo = GetPropertyInfo(someUserObject, u => u.UserID);

6
TSourceに関する最後のチェックがそこにあるのはなぜですか?ラムダは強く型付けされているので、必要ないと思います。
HappyNomad 2012

16
また、2012年の時点では、型の推論はソースパラメーターなしで正常に機能します。
HappyNomad 2012

4
@HappyNomad 3番目のタイプのインスタンスをメンバーとして持つオブジェクトを想像してみてください。u => u.OtherType.OtherTypesProperty最後のステートメントがチェックしているようなケースを作成します。
joshperry

5
最後のifステートメントは次のようになります:if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))インターフェースも許可する。
グラハムキング

8
@GrayKingはそれと同じではないif(!propInfo.ReflectedType.IsAssignableFrom(type))でしょうか?
Connell 2014年

192

あなたがそれを行うことができる別の方法は、ソースとプロパティを強く型付けしてラムダの入力を明示的に推論することでした。それが正しい用語かどうかはわかりませんが、結果は次のとおりです。

public static RouteValueDictionary GetInfo<T,P>(this HtmlHelper html, Expression<Func<T, P>> action) where T : class
{
    var expression = (MemberExpression)action.Body;
    string name = expression.Member.Name;

    return GetInfo(html, name);
}

そして、それをそのように呼びます。

GetInfo((User u) => u.UserId);

出来上がりです。
皆さんありがとう。


4
このソリューションは少し更新する必要があります。次の記事を確認してください-ここにリンクがあります
Pavel Cermak

1
ASP.Net MVCを実行する場合の唯一のオプションであり、UIレイヤー(HtmlHelper)専用です。
マルク・

3
使用できるc#6.0以降GetInfo(nameof(u.UserId))
Vladislav

1
ネットコアでは、これを使用する必要がありましたvar name = ((MemberExpression) ((UnaryExpression) accessor.Body).Operand).Member.Name
Falk

146

私は同じことで遊んでいて、これを作り上げました。完全にはテストされていませんが、値型の問題(遭遇した単項式の問題)を処理しているようです

public static string GetName(Expression<Func<object>> exp)
{
    MemberExpression body = exp.Body as MemberExpression;

    if (body == null) {
       UnaryExpression ubody = (UnaryExpression)exp.Body;
       body = ubody.Operand as MemberExpression;
    }

    return body.Member.Name;
}

2
(から最近これを試してみました別の質問、それはサブプロパティを処理しない見つけた): o => o.Thing1.Thing2戻ってくるThing2、ではないThing1.Thing2あなたはEntityFrameworkでそれを使用しようとしている場合は正しくないが含まれ、
drzaus

1
AKA(field.Body is UnaryExpression?((UnaryExpression)field.Body).Operand:field.Body)as MemberExpression

51
public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    return (Field.Body as MemberExpression ?? ((UnaryExpression)Field.Body).Operand as MemberExpression).Member.Name;
}

これは、メンバー式と単項式を処理します。違いはUnaryExpression、式が値型を表す場合はaを取得するのMemberExpressionに対して、式が参照型を表す場合はaを取得することです。すべてをオブジェクトにキャストできますが、値の型はボックス化する必要があります。これが、UnaryExpressionが存在する理由です。参照。

読みやすさ(@Jowen)のために、以下は拡張された同等物です。

public string GetName<TSource, TField>(Expression<Func<TSource, TField>> Field)
{
    if (object.Equals(Field, null))
    {
        throw new NullReferenceException("Field is required");
    }

    MemberExpression expr = null;

    if (Field.Body is MemberExpression)
    {
        expr = (MemberExpression)Field.Body;
    }
    else if (Field.Body is UnaryExpression)
    {
        expr = (MemberExpression)((UnaryExpression)Field.Body).Operand;
    }
    else
    {
        const string Format = "Expression '{0}' not supported.";
        string message = string.Format(Format, Field);

        throw new ArgumentException(message, "Field");
    }

    return expr.Member.Name;
}

@flem、読みやすくするために<TField>を省略していますが、問題はありません。LambdaExpressions.GetName <バスケット>(m => m.Quantity)
Soren

1
@soren私よりも調整されている誰かが、値型の式を渡すときに不必要なボックス化/ボックス化解除の可能性までコードを開くことを提案するかもしれませんが、このメソッドで式がコンパイルおよび評価されることはないため、それはおそらく問題ではありません。
ポールフレミング

30

C#7パターンマッチングの場合:

public static string GetMemberName<T>(this Expression<T> expression)
{
    switch (expression.Body)
    {
        case MemberExpression m:
            return m.Member.Name;
        case UnaryExpression u when u.Operand is MemberExpression m:
            return m.Member.Name;
        default:
            throw new NotImplementedException(expression.GetType().ToString());
    }
}

例:

public static RouteValueDictionary GetInfo<T>(this HtmlHelper html, 
    Expression<Func<T, object>> action) where T : class
{
    var name = action.GetMemberName();
    return GetInfo(html, name);
}

[更新] C#8パターンマッチング:

public static string GetMemberName<T>(this Expression<T> expression) =>
    expression.Body switch
    {
        MemberExpression m =>
            m.Member.Name,
        UnaryExpression u when u.Operand is MemberExpression m =>
            m.Member.Name,
        _ =>
            throw new    NotImplementedException(expression.GetType().ToString())
    };


20

これは、フィールド/プロパティ/インデクサー/メソッド/拡張メソッド/構造体/クラス/インターフェース/デリゲート/配列のデリゲートの文字列名を取得するための一般的な実装です。静的/インスタンスと非ジェネリック/ジェネリックのバリアントの組み合わせでテストしました。

//involves recursion
public static string GetMemberName(this LambdaExpression memberSelector)
{
    Func<Expression, string> nameSelector = null;  //recursive func
    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);
}

このことも簡単なwhileループで書くことができます:

//iteration based
public static string GetMemberName(this LambdaExpression memberSelector)
{
    var currentExpression = memberSelector.Body;

    while (true)
    {
        switch (currentExpression.NodeType)
        {
            case ExpressionType.Parameter:
                return ((ParameterExpression)currentExpression).Name;
            case ExpressionType.MemberAccess:
                return ((MemberExpression)currentExpression).Member.Name;
            case ExpressionType.Call:
                return ((MethodCallExpression)currentExpression).Method.Name;
            case ExpressionType.Convert:
            case ExpressionType.ConvertChecked:
                currentExpression = ((UnaryExpression)currentExpression).Operand;
                break;
            case ExpressionType.Invoke:
                currentExpression = ((InvocationExpression)currentExpression).Expression;
                break;
            case ExpressionType.ArrayLength:
                return "Length";
            default:
                throw new Exception("not a proper member selector");
        }
    }
}

私は再帰的なアプローチが好きですが、2番目のアプローチの方が読みやすいかもしれません。次のように呼び出すことができます:

someExpr = x => x.Property.ExtensionMethod()[0]; //or
someExpr = x => Static.Method().Field; //or
someExpr = x => VoidMethod(); //or
someExpr = () => localVariable; //or
someExpr = x => x; //or
someExpr = x => (Type)x; //or
someExpr = () => Array[0].Delegate(null); //etc

string name = someExpr.GetMemberName();

最後のメンバーを印刷します。

注意:

  1. のような連鎖式の場合A.B.C、「C」が返されます。

  2. これは、consts、配列インデクサー、またはsでは機能しませんenum(すべてのケースをカバーすることはできません)。


19

Array.Length に関しては、特別なケースがあります。「長さ」はプロパティとして公開されていますが、以前に提案されたソリューションでは使用できません。

using Contract = System.Diagnostics.Contracts.Contract;
using Exprs = System.Linq.Expressions;

static string PropertyNameFromMemberExpr(Exprs.MemberExpression expr)
{
    return expr.Member.Name;
}

static string PropertyNameFromUnaryExpr(Exprs.UnaryExpression expr)
{
    if (expr.NodeType == Exprs.ExpressionType.ArrayLength)
        return "Length";

    var mem_expr = expr.Operand as Exprs.MemberExpression;

    return PropertyNameFromMemberExpr(mem_expr);
}

static string PropertyNameFromLambdaExpr(Exprs.LambdaExpression expr)
{
         if (expr.Body is Exprs.MemberExpression)   return PropertyNameFromMemberExpr(expr.Body as Exprs.MemberExpression);
    else if (expr.Body is Exprs.UnaryExpression)    return PropertyNameFromUnaryExpr(expr.Body as Exprs.UnaryExpression);

    throw new NotSupportedException();
}

public static string PropertyNameFromExpr<TProp>(Exprs.Expression<Func<TProp>> expr)
{
    Contract.Requires<ArgumentNullException>(expr != null);
    Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);

    return PropertyNameFromLambdaExpr(expr);
}

public static string PropertyNameFromExpr<T, TProp>(Exprs.Expression<Func<T, TProp>> expr)
{
    Contract.Requires<ArgumentNullException>(expr != null);
    Contract.Requires<ArgumentException>(expr.Body is Exprs.MemberExpression || expr.Body is Exprs.UnaryExpression);

    return PropertyNameFromLambdaExpr(expr);
}

今の使用例:

int[] someArray = new int[1];
Console.WriteLine(PropertyNameFromExpr( () => someArray.Length ));

PropertyNameFromUnaryExprチェックしなかった場合ArrayLength、「someArray」がコンソールに出力されます(コンパイラーは、デバッグでも最適化として、バッキング長さフィールドへの直接アクセスを生成するようです。したがって、特殊なケースです)。


16

これは、Cameronによって提案されメソッドの更新です。最初のパラメーターは必須ではありません。

public PropertyInfo GetPropertyInfo<TSource, TProperty>(
    Expression<Func<TSource, TProperty>> propertyLambda)
{
    Type type = typeof(TSource);

    MemberExpression member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));

    PropertyInfo propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));

    if (type != propInfo.ReflectedType &&
        !type.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(string.Format(
            "Expresion '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));

    return propInfo;
}

次のことができます。

var propertyInfo = GetPropertyInfo<SomeType>(u => u.UserID);
var propertyInfo = GetPropertyInfo((SomeType u) => u.UserID);

拡張メソッド:

public static PropertyInfo GetPropertyInfo<TSource, TProperty>(this TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
    return GetPropertyInfo(propertyLambda);
}

public static string NameOfProperty<TSource, TProperty>(this TSource source,
    Expression<Func<TSource, TProperty>> propertyLambda) where TSource : class
{
    PropertyInfo prodInfo = GetPropertyInfo(propertyLambda);
    return prodInfo.Name;
}

あなたはできる:

SomeType someInstance = null;
string propName = someInstance.NameOfProperty(i => i.Length);
PropertyInfo propInfo = someInstance.GetPropertyInfo(i => i.Length);

いいえu、彼はタイプとして推論しません。推論するタイプがないため、彼はそれを行うことができません。あなたにできることGetPropertyInfo<SomeType>(u => u.UserID)
Lucas

14

/にドリルダウンする提案された回答の一部は、ネストされた/サブプロパティをキャプチャしないことがわかりました。MemberExpressionUnaryExpression

例)ではなくをo => o.Thing1.Thing2返します。Thing1Thing1.Thing2

EntityFrameworkを使用する場合は、この区別が重要ですDbSet.Include(...)

を解析するだけでExpression.ToString()問題なく、比較的高速に動作するようです。私はそれをUnaryExpressionバージョンと比較し、それToStringから降りMember/UnaryExpressionてそれがより速いかどうかを確認しましたが、違いは無視できました。これがひどい考えであるなら、私を訂正してください。

拡張メソッド

/// <summary>
/// Given an expression, extract the listed property name; similar to reflection but with familiar LINQ+lambdas.  Technique @via https://stackoverflow.com/a/16647343/1037948
/// </summary>
/// <remarks>Cheats and uses the tostring output -- Should consult performance differences</remarks>
/// <typeparam name="TModel">the model type to extract property names</typeparam>
/// <typeparam name="TValue">the value type of the expected property</typeparam>
/// <param name="propertySelector">expression that just selects a model property to be turned into a string</param>
/// <param name="delimiter">Expression toString delimiter to split from lambda param</param>
/// <param name="endTrim">Sometimes the Expression toString contains a method call, something like "Convert(x)", so we need to strip the closing part from the end</param>
/// <returns>indicated property name</returns>
public static string GetPropertyName<TModel, TValue>(this Expression<Func<TModel, TValue>> propertySelector, char delimiter = '.', char endTrim = ')') {

    var asString = propertySelector.ToString(); // gives you: "o => o.Whatever"
    var firstDelim = asString.IndexOf(delimiter); // make sure there is a beginning property indicator; the "." in "o.Whatever" -- this may not be necessary?

    return firstDelim < 0
        ? asString
        : asString.Substring(firstDelim+1).TrimEnd(endTrim);
}//--   fn  GetPropertyNameExtended

(区切り文字のチェックはやり過ぎかもしれません)

デモ(LinqPad)

デモ+比較コード-https://gist.github.com/zaus/6992590


1
+ 1非常に興味深い。あなた自身のコードでこのメソッドを使い続けましたか?それは大丈夫ですか?エッジケースを発見しましたか?
ベンジャミンゲイル

私はあなたの考えを見ることができません。あなたが言ったようにあなたがリンクo => o.Thing1.Thing2した答えで行くことは戻りThing1ませんがThing2。実際、あなたの答えはThing1.Thing2、望まれるかもしれないし望まれないかもしれないものを返します。
nawfal

ケースのコーマン警告では機能しません:stackoverflow.com/a/11006147/661933。ハックを回避するために常により良い。
nawfal

@nawfal#1-元々の問題はあなたが望むことであり Thing1.Thing2、決してそうではありませんThing1。述語のポイントであるThing2を意味すると述べましたo.Thing1.Thing2。その意図を反映するように答えを更新します。
drzaus 2013年

@drzaus申し訳ありません、まだあなたに連絡できません。真に理解しようとしています。ここで他の回答が返されると言うのはなぜThing1ですか?私はそれがまったくそれを元に戻すとは思わない。
nawfal 2013年

6

C#6以前のプロジェクトには拡張メソッドを使用し、C#6 を対象とするプロジェクトにはnameof()を使用しています。

public static class MiscExtentions
{
    public static string NameOf<TModel, TProperty>(this object @object, Expression<Func<TModel, TProperty>> propertyExpression)
    {
        var expression = propertyExpression.Body as MemberExpression;
        if (expression == null)
        {
            throw new ArgumentException("Expression is not a property.");
        }

        return expression.Member.Name;
    }
}

そして私はそれを次のように呼びます:

public class MyClass 
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
    public int[] Property3 { get; set; }
    public Subclass Property4 { get; set; }
    public Subclass[] Property5 { get; set; }
}

public class Subclass
{
    public int PropertyA { get; set; }
    public string PropertyB { get; set; }
}

// result is Property1
this.NameOf((MyClass o) => o.Property1);
// result is Property2
this.NameOf((MyClass o) => o.Property2);
// result is Property3
this.NameOf((MyClass o) => o.Property3);
// result is Property4
this.NameOf((MyClass o) => o.Property4);
// result is PropertyB
this.NameOf((MyClass o) => o.Property4.PropertyB);
// result is Property5
this.NameOf((MyClass o) => o.Property5);

フィールドとプロパティの両方で正常に動作します。


5

まあ、電話する必要はありません .Name.ToString()、おおまかに言ってそれについてです。必要となる可能性がある唯一の考慮事項はx.Foo.Bar、「Foo」、「Bar」、または例外を返すかどうかです。つまり、まったく繰り返す必要があるかどうかです。

(再コメント)柔軟なソートの詳細については、こちらを参照してください


ええと...それは最初のレベルのものだけであり、ソート列リンクを生成するために使用されます。例えば。モデルがあり、並べ替えに使用する列名を表示したい場合は、オブジェクトへの強く型付けされたリンクを使用して、動的linqが適用されないプロパティ名を取得できます。乾杯。
Schotime 2009年

ToString単項式の醜い結果を与えるはずです。
nawfal 2013年

3

ObjectStateEntryに拡張メソッドを作成して、(Entity Framework POCOクラスの)プロパティにタイプセーフな方法で変更されたフラグを付けることができるようにしました。デフォルトのメソッドは文字列しか受け入れないためです。プロパティから名前を取得する私の方法は次のとおりです。

public static void SetModifiedProperty<T>(this System.Data.Objects.ObjectStateEntry state, Expression<Func<T>> action)
{
    var body = (MemberExpression)action.Body;
    string propertyName = body.Member.Name;

    state.SetModifiedProperty(propertyName);
}

3

INotifyPropertyChanged以下の方法と同様の実装を行いました。ここで、プロパティは以下に示す基本クラスのディクショナリに格納されます。もちろん、継承を使用することが常に望ましいとは限りませんが、ビューモデルの場合は許容可能であり、ビューモデルクラスで非常にクリーンなプロパティ参照を提供すると思います。

public class PhotoDetailsViewModel
    : PropertyChangedNotifierBase<PhotoDetailsViewModel>
{
    public bool IsLoading
    {
        get { return GetValue(x => x.IsLoading); }
        set { SetPropertyValue(x => x.IsLoading, value); }
    }

    public string PendingOperation
    {
        get { return GetValue(x => x.PendingOperation); }
        set { SetPropertyValue(x => x.PendingOperation, value); }
    }

    public PhotoViewModel Photo
    {
        get { return GetValue(x => x.Photo); }
        set { SetPropertyValue(x => x.Photo, value); }
    }
}

やや複雑な基本クラスを以下に示します。ラムダ式からプロパティ名への変換を処理します。名前のみが使用されるため、プロパティは実際には疑似プロパティであることに注意してください。ただし、ビューモデルとビューモデルのプロパティへの参照に対して透過的に表示されます。

public class PropertyChangedNotifierBase<T> : INotifyPropertyChanged
{
    readonly Dictionary<string, object> _properties = new Dictionary<string, object>();

    protected U GetValue<U>(Expression<Func<T, U>> property)
    {
        var propertyName = GetPropertyName(property);

        return GetValue<U>(propertyName);
    }

    private U GetValue<U>(string propertyName)
    {
        object value;

        if (!_properties.TryGetValue(propertyName, out value))
        {
            return default(U);
        }

        return (U)value;
    }

    protected void SetPropertyValue<U>(Expression<Func<T, U>> property, U value)
    {
        var propertyName = GetPropertyName(property);

        var oldValue = GetValue<U>(propertyName);

        if (Object.ReferenceEquals(oldValue, value))
        {
            return;
        }
        _properties[propertyName] = value;

        RaisePropertyChangedEvent(propertyName);
    }

    protected void RaisePropertyChangedEvent<U>(Expression<Func<T, U>> property)
    {
        var name = GetPropertyName(property);
        RaisePropertyChangedEvent(name);
    }

    protected void RaisePropertyChangedEvent(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private static string GetPropertyName<U>(Expression<Func<T, U>> property)
    {
        if (property == null)
        {
            throw new NullReferenceException("property");
        }

        var lambda = property as LambdaExpression;

        var memberAssignment = (MemberExpression) lambda.Body;
        return memberAssignment.Member.Name;
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

1
あなたは基本的にプロパティバッグを維持しています。悪くありませんが、モデルクラスのゲッターとセッターからの呼び出しは、のように少し簡単ですpublic bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }。遅いかもしれませんが、より一般的で簡単です。
nawfal 2013

実際に単純な依存関係プロパティシステムを実装することは困難です(しかしそれほど難しくはありません)が、実際には上記の実装よりもはるかに高いパフォーマンスを発揮します。
Felix K.

3

これは別の答えです:

public static string GetPropertyName<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                      Expression<Func<TModel, TProperty>> expression)
    {
        var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        return metaData.PropertyName;
    }

1
ModelMetadataSystem.Web.Mvc名前空間に存在します。多分それは一般的なケースに適合しないでしょう
asakura89

3

複数のフィールドを取得する場合は、この関数を終了します。

/// <summary>
    /// Get properties separated by , (Ex: to invoke 'd => new { d.FirstName, d.LastName }')
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="exp"></param>
    /// <returns></returns>
    public static string GetFields<T>(Expression<Func<T, object>> exp)
    {
        MemberExpression body = exp.Body as MemberExpression;
        var fields = new List<string>();
        if (body == null)
        {
            NewExpression ubody = exp.Body as NewExpression;
            if (ubody != null)
                foreach (var arg in ubody.Arguments)
                {
                    fields.Add((arg as MemberExpression).Member.Name);
                }
        }

        return string.Join(",", fields);
    }

3
これを説明しますか?

1

この回答に基づいてPropertyInfoを取得する別の方法を次に示します。オブジェクトインスタンスの必要がなくなります。

/// <summary>
/// Get metadata of property referenced by expression. Type constrained.
/// </summary>
public static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda)
{
    return GetPropertyInfo((LambdaExpression) propertyLambda);
}

/// <summary>
/// Get metadata of property referenced by expression.
/// </summary>
public static PropertyInfo GetPropertyInfo(LambdaExpression propertyLambda)
{
    // /programming/671968/retrieving-property-name-from-lambda-expression
    MemberExpression member = propertyLambda.Body as MemberExpression;
    if (member == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));

    PropertyInfo propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
        throw new ArgumentException(string.Format(
            "Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));

    if(propertyLambda.Parameters.Count() == 0)
        throw new ArgumentException(String.Format(
            "Expression '{0}' does not have any parameters. A property expression needs to have at least 1 parameter.",
            propertyLambda.ToString()));

    var type = propertyLambda.Parameters[0].Type;
    if (type != propInfo.ReflectedType &&
        !type.IsSubclassOf(propInfo.ReflectedType))
        throw new ArgumentException(String.Format(
            "Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(),
            type));
    return propInfo;
}

次のように呼び出すことができます。

var propertyInfo = GetPropertyInfo((User u) => u.UserID);

1

@Cameronの回答を更新して、Convert型指定されたラムダ式に対するいくつかの安全性チェックを含めました。

PropertyInfo GetPropertyName<TSource, TProperty>(
Expression<Func<TSource, TProperty>> propertyLambda)
{
  var body = propertyLambda.Body;
  if (!(body is MemberExpression member)
    && !(body is UnaryExpression unary
      && (member = unary.Operand as MemberExpression) != null))
    throw new ArgumentException($"Expression '{propertyLambda}' " +
      "does not refer to a property.");

  if (!(member.Member is PropertyInfo propInfo))
    throw new ArgumentException($"Expression '{propertyLambda}' " +
      "refers to a field, not a property.");

  var type = typeof(TSource);
  if (!propInfo.DeclaringType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
    throw new ArgumentException($"Expresion '{propertyLambda}' " + 
      "refers to a property that is not from type '{type}'.");

  return propInfo;
}

1

.NET 4.0以降ではExpressionVisitor、プロパティの検索に使用できます。

class ExprVisitor : ExpressionVisitor {
    public bool IsFound { get; private set; }
    public string MemberName { get; private set; }
    public Type MemberType { get; private set; }
    protected override Expression VisitMember(MemberExpression node) {
        if (!IsFound && node.Member.MemberType == MemberTypes.Property) {
            IsFound = true;
            MemberName = node.Member.Name;
            MemberType = node.Type;
        }
        return base.VisitMember(node);
    }
}

このビジターの使い方は次のとおりです。

var visitor = new ExprVisitor();
visitor.Visit(expr);
if (visitor.IsFound) {
    Console.WriteLine("First property in the expression tree: Name={0}, Type={1}", visitor.MemberName, visitor.MemberType.FullName);
} else {
    Console.WriteLine("No properties found.");
}

1

これは最適かもしれません

public static string GetPropertyName<TResult>(Expression<Func<TResult>> expr)
{
    var memberAccess = expr.Body as MemberExpression;
    var propertyInfo = memberAccess?.Member as PropertyInfo;
    var propertyName = propertyInfo?.Name;

    return propertyName;
}

0
static void Main(string[] args)
{
    var prop = GetPropertyInfo<MyDto>(_ => _.MyProperty);

    MyDto dto = new MyDto();
    dto.MyProperty = 666;

    var value = prop.GetValue(dto);
    // value == 666
}

class MyDto
{
    public int MyProperty { get; set; }
}

public static PropertyInfo GetPropertyInfo<TSource>(Expression<Func<TSource, object>> propertyLambda)
{
    Type type = typeof(TSource);

    var member = propertyLambda.Body as MemberExpression;
    if (member == null)
    {
        var unary = propertyLambda.Body as UnaryExpression;
        if (unary != null)
        {
            member = unary.Operand as MemberExpression;
        }
    }
    if (member == null)
    {
        throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.",
            propertyLambda.ToString()));
    }

    var propInfo = member.Member as PropertyInfo;
    if (propInfo == null)
    {
        throw new ArgumentException(string.Format("Expression '{0}' refers to a field, not a property.",
            propertyLambda.ToString()));
    }

    if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType))
    {
        throw new ArgumentException(string.Format("Expression '{0}' refers to a property that is not from type {1}.",
            propertyLambda.ToString(), type));
    }

    return propInfo;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.