รับชื่อคุณสมบัติเป็นสตริง


203

(ดูวิธีแก้ปัญหาด้านล่างที่ฉันสร้างโดยใช้คำตอบที่ฉันยอมรับ)

ฉันพยายามปรับปรุงความสามารถในการบำรุงรักษาของโค้ดบางอย่างที่เกี่ยวกับการสะท้อนกลับ แอปนี้มี. NET Remoting interface ซึ่งเป็นวิธีการที่เรียกว่า Execute สำหรับการเข้าถึงส่วนต่าง ๆ ของแอพซึ่งไม่ได้รวมอยู่ในอินเทอร์เฟซระยะไกลที่เผยแพร่

นี่คือวิธีที่แอปกำหนดคุณสมบัติ (แบบคงที่ในตัวอย่างนี้) ซึ่งหมายถึงให้สามารถเข้าถึงได้ผ่าน Execute:

RemoteMgr.ExposeProperty("SomeSecret", typeof(SomeClass), "SomeProperty");

ดังนั้นผู้ใช้ระยะไกลสามารถโทร:

string response = remoteObject.Execute("SomeSecret");

และแอพจะใช้การสะท้อนเพื่อค้นหา SomeClass.SomeProperty และส่งคืนค่าเป็นสตริง

น่าเสียดายที่ถ้ามีคนเปลี่ยนชื่อ SomeProperty และลืมเปลี่ยน parm ที่ 3 ของ ExposeProperty () มันจะทำลายกลไกนี้

ฉันต้องการเทียบเท่า:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

ใช้เป็น parm ตัวที่ 3 ใน ExposeProperty ดังนั้นเครื่องมือการรีแฟคเตอร์จะดูแลการเปลี่ยนชื่อ

มีวิธีทำเช่นนี้หรือไม่? ขอบคุณล่วงหน้า.

ตกลงนี่คือสิ่งที่ฉันสร้างขึ้น (ตามคำตอบที่ฉันเลือกและคำถามที่เขาอ้างถึง):

// <summary>
// Get the name of a static or instance property from a property access lambda.
// </summary>
// <typeparam name="T">Type of the property</typeparam>
// <param name="propertyLambda">lambda expression of the form: '() => Class.Property' or '() => object.Property'</param>
// <returns>The name of the property</returns>
public string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
{
    var me = propertyLambda.Body as MemberExpression;

    if (me == null)
    {
        throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
    }

    return me.Member.Name;
 }

การใช้งาน:

// Static Property
string name = GetPropertyName(() => SomeClass.SomeProperty);

// Instance Property
string name = GetPropertyName(() => someObject.SomeProperty);

ขณะนี้มีความสามารถที่ยอดเยี่ยมนี้ถึงเวลาแล้วที่จะลดความซับซ้อนของวิธี ExposeProperty การขัดลูกบิดประตูเป็นงานที่อันตราย ...

ขอบคุณทุกคน


9
เห็นคุณค่าจริง ๆ ว่าคุณเพิ่มโซลูชันของคุณและเชื่อมโยงสิ่งต่างๆเข้าด้วยกัน
เพียงแค่ G.


คุณควรเพิ่มโซลูชันเป็นคำตอบ - มันกระชับกว่าคำตอบที่คุณยอมรับ
Kenny Evitt

1
@Kenny Evitt: เสร็จแล้ว:)
จิมซี

@JimC โหวตแล้ว! และเชื่อมโยงในการแสดงความคิดเห็นในคำตอบที่ได้รับการยอมรับในขณะนี้ ขอบคุณ!
Kenny Evitt

คำตอบ:


61

ใช้ GetMemberInfo จากที่นี่: การดึงชื่อคุณสมบัติจากแลมบ์ดานิพจน์คุณสามารถทำสิ่งนี้:

RemoteMgr.ExposeProperty(() => SomeClass.SomeProperty)

public class SomeClass
{
    public static string SomeProperty
    {
        get { return "Foo"; }
    }
}

public class RemoteMgr
{
    public static void ExposeProperty<T>(Expression<Func<T>> property)
    {
        var expression = GetMemberInfo(property);
        string path = string.Concat(expression.Member.DeclaringType.FullName,
            ".", expression.Member.Name);
        // Do ExposeProperty work here...
    }
}

public class Program
{
    public static void Main()
    {
        RemoteMgr.ExposeProperty("SomeSecret", () => SomeClass.SomeProperty);
    }
}

มันยอดเยี่ยมมาก ดูเหมือนว่ามันจะทำงานกับประเภททรัพย์สินใด ๆ เช่นกัน
จิมซี

ฉันแค่ลองกับทั้งสองตัวอย่างและคุณสมบัติคงที่ จนถึงตอนนี้ดีมาก
Jim C

ความคิดใดที่ฉันจะได้รับการประกอบหรือแพคเกจ NuGet ที่มีGetMemberInfo? ฉันไม่พบสิ่งใดด้วยแพ็คเกจ 'ยูทิลิตี้ทั่วไป' สำหรับ Microsoft Enterprise Library ซึ่งเป็นสิ่งที่ MSDN ดูเหมือนว่าจะระบุว่ามีวิธีการนั้น มีแพ็คเกจ "ไม่เป็นทางการ" แต่การไม่เป็นทางการนั้นไม่น่าสนใจเลย คำตอบของ JimCซึ่งมีพื้นฐานมาจากคำตอบนี้มีความรัดกุมมากกว่าและไม่ต้องพึ่งพาห้องสมุดที่ดูเหมือนจะใช้งานไม่ได้
Kenny Evitt

1
@ KennyEvitt วิธีการอ้างอิงของเขาเป็นหนึ่งในผู้เขียนคำถามที่เขาเชื่อมโยง ทางเลือกอื่นสำหรับ methodyou นั้นสามารถใช้ Type.GetMembers msdn.microsoft.com/en-us/library/…
Bon

464

ด้วย C # 6.0 นี่เป็นปัญหาที่คุณไม่สามารถทำได้:

nameof(SomeProperty)

"SomeProperty"สำนวนนี้ได้รับการแก้ไขที่รวบรวมเวลาที่จะ

เอกสาร MSDN ของ nameof


18
นี่เป็น badass และมีประโยชน์มากสำหรับการโทร ModelState.AddModelError
Michael Silver

9
และนี่คือconst string! น่าทึ่ง
แจ็ค

4
@RaidenCore แน่นอนว่าถ้าคุณเขียนไมโครคอนโทรลเลอร์คุณควรใช้ภาษาระดับต่ำเช่น C และถ้าคุณต้องการบีบประสิทธิภาพทุกอย่างเช่นการประมวลผลภาพและวิดีโอคุณควรใช้ C หรือ C ++ แต่สำหรับแอพพลิเคชั่นอื่น ๆ 95% กรอบการทำงานของโค้ดที่ถูกจัดการจะเร็วพอ ในที่สุด C # จะถูกคอมไพล์ไปยังรหัสเครื่องและคุณสามารถรวบรวมล่วงหน้าเป็นภาษาท้องถิ่นหากคุณต้องการ
Tsahi Asher

2
โดยวิธีการ @RaidenCore แอปที่คุณพูดถึงก่อนวัน C # นั่นคือสาเหตุที่พวกเขาเขียนใน C ++ ถ้าพวกเขาเขียนวันนี้ใครจะรู้ว่าใช้ภาษาอะไร ดูเช่น Paint.NET
Tsahi Asher

1
นี่เป็นประโยชน์จริง ๆ เมื่อคุณต้องการRaisePropertyใน WPF! ใช้ RaisePropertyChanged (nameof (คุณสมบัติ)) แทน RaisePropertyChanged ("คุณสมบัติ")
Pierre

17

มีแฮ็คที่รู้จักกันดีในการดึงมันออกมาจากการแสดงออกแลมบ์ดา (นี่คือจากคลาส PropertyObserver โดย Josh Smith ในรากฐาน MVVM ของเขา):

    private static string GetPropertyName<TPropertySource>
        (Expression<Func<TPropertySource, object>> expression)
    {
        var lambda = expression as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }

        Debug.Assert(memberExpression != null, 
           "Please provide a lambda expression like 'n => n.PropertyName'");

        if (memberExpression != null)
        {
            var propertyInfo = memberExpression.Member as PropertyInfo;

            return propertyInfo.Name;
        }

        return null;
    }

ขออภัยนี่ไม่มีบริบทบางอย่าง นี่เป็นส่วนหนึ่งของคลาสที่ใหญ่กว่าซึ่งคลาสTPropertySourceนั้นมีคุณสมบัติอยู่ คุณสามารถทำให้ฟังก์ชั่นทั่วไปใน TPropertySource เพื่อดึงมันออกมาจากชั้นเรียน ผมขอแนะนำการดูที่รหัสเต็มรูปแบบจากที่มูลนิธิ MVVM


ด้วยตัวอย่างของวิธีเรียกใช้ฟังก์ชันนี่คือ +1 อย่างแน่นอน โอ๊ะไม่เห็นว่ามีหนึ่งในการยืนยันการแก้ปัญหา - นั่นเป็นเหตุผลที่ทำให้นักพัฒนาเลื่อนแนวนอนเพื่อไปยังส่วนที่สำคัญของบรรทัดเป็นสิ่งชั่วร้าย;)
OregonGhost

อืม ... ฉันต้องผ่าอันนี้เพื่อที่จะเข้าใจ
จิมซี

Visual Studio 2008 ตั้งค่าสถานะ "TPropertySource" เป็นข้อผิดพลาด (ไม่พบ "")
จิมซี

ฉันเพิ่งรู้ว่าเป็นชื่อประเภทไม่ใช่แค่สัญลักษณ์ <T> เหมือนใน C ++ TPropertySource เป็นตัวแทนอะไร
จิมซี

2
ที่จะทำให้การรวบรวมนี้คุณก็สามารถเปลี่ยนลายเซ็นของวิธีการที่จะอ่านpublic static string GetPropertyName<TPropertySource>(Expression<Func<TPropertySource, object>> expression)แล้วโทรชอบโดย:var name = GetPropertyName<TestClass>(x => x.Foo);
dav_i

16

ตกลงนี่คือสิ่งที่ฉันสร้างขึ้น (ตามคำตอบที่ฉันเลือกและคำถามที่เขาอ้างถึง):

// <summary>
// Get the name of a static or instance property from a property access lambda.
// </summary>
// <typeparam name="T">Type of the property</typeparam>
// <param name="propertyLambda">lambda expression of the form: '() => Class.Property' or '() => object.Property'</param>
// <returns>The name of the property</returns>

public string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
{
    var me = propertyLambda.Body as MemberExpression;

    if (me == null)
    {
        throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
    }

    return me.Member.Name;
 }

การใช้งาน:

// Static Property
string name = GetPropertyName(() => SomeClass.SomeProperty);

// Instance Property
string name = GetPropertyName(() => someObject.SomeProperty);

8

PropertyInfoชั้นควรจะช่วยให้คุณบรรลุนี้ถ้าผมเข้าใจอย่างถูกต้อง

  1. Type.GetProperties () วิธีการ

    PropertyInfo[] propInfos = typeof(ReflectedType).GetProperties();
    propInfos.ToList().ForEach(p => 
        Console.WriteLine(string.Format("Property name: {0}", p.Name));

นี่คือสิ่งที่คุณต้องการ?


ไม่แม้ว่าฉันจะใช้ GetProperties เมื่อแอพได้รับคำขอ "SomeSecret" แอพพลิเคชั่นค้นหา "SomeSecret" ในแผนที่เพื่อค้นหาความต้องการค้นหาคุณสมบัติที่ชื่อว่า "SomeProperty" ในคลาสที่เรียกว่า "SomeClass"
จิมซี

nameof (SomeProperty) ช่วยให้เรื่องนี้ง่ายขึ้นจาก. net 4.0 เป็นต้นไป ไม่จำเป็นต้องแฮ็กนาน
Div Tiwari

6

คุณสามารถใช้ Reflection เพื่อรับชื่อจริงของคุณสมบัติ

http://www.csharp-examples.net/reflection-property-names/

หากคุณต้องการวิธีกำหนด "ชื่อสตริง" ให้กับคุณสมบัติทำไมคุณไม่เขียนแอตทริบิวต์ที่คุณสามารถสะท้อนกลับเพื่อรับชื่อสตริง

[StringName("MyStringName")]
private string MyProperty
{
    get { ... }
}

1
ใช่นั่นคือวิธีที่แอปจัดการคำขอที่เข้ามาสำหรับ "SomeSecret" แต่มันไม่ได้ให้เครื่องมือสำหรับปัญหา ExposeProperty กับฉัน
จิมซี

ที่น่าสนใจ ... จากนั้นคุณสามารถเปลี่ยนชื่อ MyProperty เป็นเนื้อหาในใจตราบใดที่คุณไม่ยุ่งกับ MyStringName และถ้าคุณต้องการเปลี่ยนมันด้วยเหตุผลบางอย่างคุณต้องแก้ไข ExposeProperty parm อย่างน้อยฉันสามารถเพิ่มความคิดเห็นถัดจากแอททริบิวด้วยคำเตือนเช่นนี้เนื่องจากคุณต้องมองมันเพื่อเปลี่ยนค่าของแอททริบิวต์ (ต่างจากการเปลี่ยนชื่อคุณสมบัติซึ่งสามารถทำได้จากตำแหน่งอ้างอิงใด ๆ )
จิมซี

6

ฉันแก้ไขโซลูชันของคุณเพื่อโยงคุณสมบัติหลาย ๆ อย่าง:

public static string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
{
    MemberExpression me = propertyLambda.Body as MemberExpression;
    if (me == null)
    {
        throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
    }

    string result = string.Empty;
    do
    {
        result = me.Member.Name + "." + result;
        me = me.Expression as MemberExpression;
    } while (me != null);

    result = result.Remove(result.Length - 1); // remove the trailing "."
    return result;
}

การใช้งาน:

string name = GetPropertyName(() => someObject.SomeProperty.SomeOtherProperty);
// returns "SomeProperty.SomeOtherProperty"

4

ตามคำตอบที่มีอยู่แล้วในคำถามและจากบทความนี้: https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/ I กำลังนำเสนอวิธีแก้ไขปัญหาของฉัน:

public static class PropertyNameHelper
{
    /// <summary>
    /// A static method to get the Propertyname String of a Property
    /// It eliminates the need for "Magic Strings" and assures type safety when renaming properties.
    /// See: http://stackoverflow.com/questions/2820660/get-name-of-property-as-a-string
    /// </summary>
    /// <example>
    /// // Static Property
    /// string name = PropertyNameHelper.GetPropertyName(() => SomeClass.SomeProperty);
    /// // Instance Property
    /// string name = PropertyNameHelper.GetPropertyName(() => someObject.SomeProperty);
    /// </example>
    /// <typeparam name="T"></typeparam>
    /// <param name="propertyLambda"></param>
    /// <returns></returns>
    public static string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
    {
        var me = propertyLambda.Body as MemberExpression;

        if (me == null)
        {
            throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
        }

        return me.Member.Name;
    }
    /// <summary>
    /// Another way to get Instance Property names as strings.
    /// With this method you don't need to create a instance first.
    /// See the example.
    /// See: https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/
    /// </summary>
    /// <example>
    /// string name = PropertyNameHelper((Firma f) => f.Firmenumsatz_Waehrung);
    /// </example>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TReturn"></typeparam>
    /// <param name="expression"></param>
    /// <returns></returns>
    public static string GetPropertyName<T, TReturn>(Expression<Func<T, TReturn>> expression)
    {
        MemberExpression body = (MemberExpression)expression.Body;
        return body.Member.Name;
    }
}

และการทดสอบที่แสดงการใช้งานสำหรับอินสแตนซ์และคุณสมบัติแบบคงที่:

[TestClass]
public class PropertyNameHelperTest
{
    private class TestClass
    {
        public static string StaticString { get; set; }
        public string InstanceString { get; set; }
    }

    [TestMethod]
    public void TestGetPropertyName()
    {
        Assert.AreEqual("StaticString", PropertyNameHelper.GetPropertyName(() => TestClass.StaticString));

        Assert.AreEqual("InstanceString", PropertyNameHelper.GetPropertyName((TestClass t) => t.InstanceString));
    }
}

3

คำถามเก่า แต่คำตอบอื่นสำหรับคำถามนี้คือการสร้างฟังก์ชันแบบคงที่ในคลาสตัวช่วยที่ใช้ CallerMemberNameAttribute

public static string GetPropertyName([CallerMemberName] String propertyName = null) {
  return propertyName;
}

แล้วใช้มันเช่น:

public string MyProperty {
  get { Console.WriteLine("{0} was called", GetPropertyName()); return _myProperty; }
}

0

คุณสามารถใช้คลาส StackTrace เพื่อรับชื่อฟังก์ชั่นปัจจุบัน (หรือถ้าคุณใส่รหัสในฟังก์ชั่นจากนั้นลดระดับและรับฟังก์ชั่นการโทร)

ดูhttp://msdn.microsoft.com/en-us/library/system.diagnostics.stacktrace(VS.71).aspx


ฉันไม่รู้ว่าคุณมีจุดประสงค์ในการจับภาพการติดตามสแต็ก แต่ฉันไม่สามารถคิดถึงสิ่งที่จะมีชื่อของคุณสมบัติ
จิมซี

คุณสามารถทำได้ แต่สิ่งนี้สามารถนำไปสู่ผลลัพธ์ที่ไม่คาดคิด (รวมถึงข้อยกเว้น) เนื่องจากคอมไพเลอร์อินไลน์การปรับให้เหมาะสม smelser.net/blog/post/2008/11/27/…
JoeGeeky

0

ฉันใช้คำตอบนี้เพื่อผลที่ยอดเยี่ยม: รับคุณสมบัติเป็นสตริงจาก Expression <Func <TModel, TProperty >>

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


0

ฉันมีปัญหาในการใช้โซลูชันที่แนะนำสำหรับกรณีการใช้งานเฉพาะของฉันแล้ว แต่คิดในท้ายที่สุด ฉันไม่คิดว่ากรณีเฉพาะของฉันมีค่าควรแก่คำถามใหม่ดังนั้นฉันจึงโพสต์โซลูชันของฉันที่นี่เพื่ออ้างอิง (นี่เป็นคำถามที่เกี่ยวข้องอย่างใกล้ชิดและเป็นวิธีแก้ปัญหาสำหรับคนอื่นที่มีกรณีคล้ายกันกับฉัน)

รหัสที่ฉันได้รับมีลักษณะดังนี้:

public class HideableControl<T>: Control where T: class
{
    private string _propertyName;
    private PropertyInfo _propertyInfo;

    public string PropertyName
    {
        get { return _propertyName; }
        set
        {
            _propertyName = value;
            _propertyInfo = typeof(T).GetProperty(value);
        }
    }

    protected override bool GetIsVisible(IRenderContext context)
    {
        if (_propertyInfo == null)
            return false;

        var model = context.Get<T>();

        if (model == null)
            return false;

        return (bool)_propertyInfo.GetValue(model, null);
    }

    protected void SetIsVisibleProperty(Expression<Func<T, bool>> propertyLambda)
    {
        var expression = propertyLambda.Body as MemberExpression;
        if (expression == null)
            throw new ArgumentException("You must pass a lambda of the form: 'vm => vm.Property'");

        PropertyName = expression.Member.Name;
    }
}

public interface ICompanyViewModel
{
    string CompanyName { get; }
    bool IsVisible { get; }
}

public class CompanyControl: HideableControl<ICompanyViewModel>
{
    public CompanyControl()
    {
        SetIsVisibleProperty(vm => vm.IsVisible);
    }
}

ส่วนที่สำคัญสำหรับฉันคือในCompanyControlชั้นเรียนคอมไพเลอร์จะอนุญาตให้ฉันเลือกคุณสมบัติบูลีนเท่านั้นICompanyViewModelซึ่งทำให้ผู้พัฒนารายอื่น ๆ สามารถทำให้ถูกต้องได้ง่ายขึ้น

ข้อแตกต่างที่สำคัญระหว่างโซลูชันของฉันกับคำตอบที่ยอมรับคือคลาสของฉันเป็นแบบทั่วไปและฉันต้องการจับคู่คุณสมบัติจากประเภททั่วไปที่เป็นบูลีนเท่านั้น


0

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

public static string GetName<TClass>(Expression<Func<TClass, 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;
}

การใช้งานเป็นเช่นนี้

var label = ClassExtension.GetName<SomeClass>(x => x.Label); //x is refering to 'SomeClass'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.