การดึงชื่อคุณสมบัติจากแลมบ์ดานิพจน์


513

มีวิธีที่ดีกว่าที่จะได้รับชื่อทรัพย์สินเมื่อส่งผ่านผ่านการแสดงออกแลมบ์ดา? นี่คือสิ่งที่ฉันมีอยู่ในปัจจุบัน

เช่น.

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

มันทำงานโดยการหล่อมันเป็น memberexpression เฉพาะเมื่อคุณสมบัติเป็นสตริง เพราะคุณสมบัติไม่ทั้งหมดเป็นสตริงที่ฉันต้องใช้วัตถุ แต่แล้วมันจะส่งกลับ unaryexpression สำหรับเหล่านั้น

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

ใช่ ... เพียงแค่สงสัยว่ามีวิธีที่ดีกว่าในการทำเช่นนี้เพราะฉันรู้สึกแฮ็คเล็กน้อยกับฉัน แต่ถ้าอย่างนั้นมันก็เจ๋ง ขอบคุณ
Schotime

ฉันได้อัพเดตความคิดเห็นของคุณแล้ว แต่การใช้แลมบ์ดารับสตริงเพื่อให้คุณสามารถใช้ไดนามิก LINQ ทำให้ฉันทำสิ่งต่าง ๆ ย้อนหลังได้ ... ถ้าคุณใช้แลมบ์ดาใช้แลมบ์ดา ;-p คุณไม่ต้องทำแบบสอบถามทั้งหมดในขั้นตอนเดียว - คุณสามารถใช้ "ปกติ / แลมบ์ดา" OrderBy "LINQ แบบไดนามิก / สตริง" ที่ไหน ฯลฯ
Marc Gravell

1
เป็นไปได้ที่ซ้ำกันของget-property-name-and-type-using-lambda-expression
nawfal

4
หมายเหตุถึงทุกคน: ใช้MemberExpressionวิธีการที่ระบุไว้ที่นี่เพื่อรับชื่อสมาชิกเท่านั้นเพื่อไม่ให้เกิดขึ้นจริงMemberInfoเนื่องจากการMemberInfoส่งคืนนั้นไม่รับประกันว่าจะเป็นประเภทที่สะท้อนในบางสถานการณ์ "dervied: base" ดูแลมบ์ดาแสดงออกที่ไม่กลับมาคาด-MemberInfo สะดุดฉันหนึ่งครั้ง คำตอบที่ได้รับการยอมรับก็มีความทุกข์เช่นกัน
nawfal

คำตอบ:


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

16
นอกจากนี้ในปี 2012 อนุมานประเภททำงานได้ดีโดยไม่มีพารามิเตอร์ต้นทาง
HappyNomad

4
@HappyNomad ลองนึกภาพวัตถุที่มีสมาชิกเป็นตัวอย่างประเภทที่สาม u => u.OtherType.OtherTypesPropertyจะสร้างกรณีที่คำสั่งล่าสุดกำลังตรวจสอบ
joshperry

5
คำสั่ง if สุดท้ายควรเป็น: if (type != propInfo.ReflectedType && !type.IsSubclassOf(propInfo.ReflectedType) && !propInfo.ReflectedType.IsAssignableFrom(type))เพื่ออนุญาตให้มีอินเตอร์เฟสเช่นกัน
Graham King

8
@GrayKing จะไม่เหมือนเดิมif(!propInfo.ReflectedType.IsAssignableFrom(type))ใช่มั้ย
Connell

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

และ voila ใช้งานได้
ขอบคุณทุกคน


4
โซลูชันนี้ควรได้รับการปรับปรุงเล็กน้อย กรุณาตรวจสอบบทความต่อไปนี้ - นี่คือลิงค์
Pavel Cermak

1
มันเป็นเพียงตัวเลือกถ้าคุณใช้ ASP.Net MVC และสำหรับเลเยอร์ UI (HtmlHelper) เท่านั้น
Marc

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 (ฟิลด์เป็น Unary Expression หรือไม่ (UnaryExpression) ฟิลด์เป็นร่าง) .Operand: fieldBody) เป็น 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ถ้าการแสดงออกของคุณแสดงให้เห็นถึงประเภทของค่าในขณะที่คุณจะได้รับMemberExpressionถ้าการแสดงออกของคุณแสดงถึงประเภทการอ้างอิง ทุกอย่างสามารถส่งไปยังวัตถุ แต่ต้องพิมพ์ค่าชนิดกล่อง นี่คือเหตุผลที่ Unary Expression มีอยู่ การอ้างอิง

เพื่อประโยชน์ในการอ่าน (@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 <Basket> (m => m.Quantity)
Soren

1
@soren ฉันแน่ใจว่ามีคนที่ปรับมากกว่าฉันอาจแนะนำให้คุณเปิดรหัสของคุณถึงศักยภาพของการชกมวย / ไม่จำเป็นเมื่อทำการส่งนิพจน์ประเภทค่า แต่เนื่องจากนิพจน์ไม่ได้รวบรวมและประเมินในวิธีนี้ มันอาจจะไม่ใช่ปัญหา
Paul Fleming

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

นี่คือการใช้งานทั่วไปเพื่อรับชื่อสตริงของฟิลด์ / คุณสมบัติ / ดัชนี / วิธี / ส่วนขยาย / ผู้รับมอบสิทธิ์ของ struct / class / interface / มอบหมาย / อาร์เรย์ ฉันได้ทดสอบกับชุดค่าผสมของแบบคงที่ / อินสแตนซ์และตัวแปรที่ไม่ใช่แบบทั่วไป / ทั่วไป

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

ฉันชอบวิธีเรียกซ้ำแม้ว่าวิธีที่สองอาจอ่านง่ายกว่า หนึ่งสามารถเรียกว่าชอบ:

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, indexers array หรือenums (เป็นไปไม่ได้ที่จะครอบคลุมทุกกรณี)


19

มีตัวพิมพ์ขอบเมื่อมันมาถึงความArrayยาว ในขณะที่ 'ความยาว' ถูกเปิดเผยเป็นคุณสมบัติคุณไม่สามารถใช้งานได้ในโซลูชันที่เสนอมาก่อนหน้านี้

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" จะถูกพิมพ์ไปที่คอนโซล (คอมไพเลอร์ดูเหมือนจะสร้างการเข้าถึงโดยตรงไปยังเขตข้อมูลความยาวสำรองเป็นการเพิ่มประสิทธิภาพแม้ใน Debug ดังนั้นกรณีพิเศษ)


16

นี่คือการปรับปรุงไปยังวิธีที่นำเสนอโดยคาเมรอน ไม่จำเป็นต้องใช้พารามิเตอร์แรก

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)
ลู

14

ฉันพบว่าบางคำตอบที่แนะนำซึ่งเจาะลึกลงในMemberExpression/ UnaryExpressionไม่จับภาพซ้อน / คุณสมบัติย่อย

อดีต) o => o.Thing1.Thing2ผลตอบแทนมากกว่าThing1Thing1.Thing2

ความแตกต่างนี้เป็นสิ่งสำคัญถ้าคุณกำลังพยายามที่จะทำงานร่วมกับ DbSet.Include(...)EntityFramework

ฉันพบว่าการแยกวิเคราะห์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 น่าสนใจมาก คุณยังคงใช้วิธีนี้ในรหัสของคุณเองหรือไม่? มันใช้งานได้ไหม คุณได้ค้นพบกรณีขอบใด ๆ ?
Benjamin Gale

ฉันไม่เห็นความคิดของคุณ ไปตามคำตอบที่คุณเชื่อมโยงo => o.Thing1.Thing2ไม่กลับมาThing1ตามที่คุณพูด Thing2แต่ ในความเป็นจริงคำตอบของคุณส่งคืนสิ่งที่ต้องการThing1.Thing2หรืออาจไม่ต้องการ
nawfal

ไม่ได้ทำงานกับกรณีข้อควรระวัง Korman นี้: stackoverflow.com/a/11006147/661933 ดีกว่าเสมอเพื่อหลีกเลี่ยงการแฮ็ก
nawfal

@nawfal # 1 - ปัญหาเดิมคือการที่คุณต้องการ ไม่เคยThing1.Thing2 Thing1ฉันพูดThing2ถึงความหมายถึงคุณค่าของo.Thing1.Thing2ซึ่งเป็นประเด็นของภาคแสดง ฉันจะอัปเดตคำตอบเพื่อสะท้อนความตั้งใจนั้น
drzaus

@drzaus ขอโทษฉันยังไม่ได้รับคุณ พยายามทำความเข้าใจอย่างแท้จริง ทำไมคุณถึงบอกว่าคำตอบอื่น ๆ ที่นี่กลับมาThing1? ฉันไม่คิดว่ามันจะแก้แค้นอีกเลย
nawfal

6

ฉันใช้วิธีการขยายสำหรับโปรเจ็กต์ C # 6 ก่อนหน้านี้และชื่อของ()สำหรับการกำหนดเป้าหมาย C # 6

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

ToStringควรให้ผลลัพธ์ที่น่าเกลียดสำหรับนิพจน์ที่ไม่น่าสนใจ
nawfal

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
โดยทั่วไปคุณกำลังดูแลกระเป๋าทรัพย์สิน ไม่เลว แต่สายที่มาจาก getters และ setters ของการเรียนรูปแบบเล็ก ๆ น้อย ๆ public bool IsLoading { get { return GetValue(MethodBase.GetCurrentMethod().Name); } set { SetPropertyValue(MethodBase.GetCurrentMethod().Name, value); } }ได้ง่ายขึ้นเช่น อาจช้ากว่า แต่ทั่วไปกว่าและตรงไปตรงมา
nawfal

การใช้ระบบทรัพย์สินที่พึ่งพาง่ายจริง ๆ แล้วจะยากกว่า (แต่ไม่มากนัก) แต่จริงๆแล้วมีประสิทธิภาพมากกว่าการใช้งานข้างต้น
เฟลิกซ์เค

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
ModelMetadataมีอยู่ในSystem.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.