Lấy tên tài sản dưới dạng chuỗi


203

(Xem giải pháp bên dưới tôi đã tạo bằng câu trả lời tôi chấp nhận)

Tôi đang cố gắng cải thiện khả năng duy trì của một số mã liên quan đến sự phản chiếu. Ứng dụng này có giao diện .NET Remote (trong số những thứ khác), một phương thức gọi là Execute để truy cập các phần của ứng dụng không có trong giao diện từ xa được xuất bản của nó.

Dưới đây là cách ứng dụng chỉ định các thuộc tính (một tĩnh trong ví dụ này) có nghĩa là có thể truy cập được qua Execute:

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

Vì vậy, một người dùng từ xa có thể gọi:

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

và ứng dụng sẽ sử dụng sự phản chiếu để tìm một sốClass.SomeProperty và trả về giá trị của nó dưới dạng một chuỗi.

Thật không may, nếu ai đó đổi tên someProperty và quên thay đổi parm thứ 3 của ExposeProperty (), nó sẽ phá vỡ cơ chế này.

Tôi cần tương đương với:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

để sử dụng như là parm thứ 3 trong ExposeProperty, vì vậy các công cụ tái cấu trúc sẽ đảm nhiệm việc đổi tên.

Có cách nào để làm việc này không? Cảm ơn trước.

Được rồi, đây là những gì tôi đã tạo ra (dựa trên câu trả lời tôi đã chọn và câu hỏi mà anh ấy tham khảo):

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

Sử dụng:

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

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

Bây giờ với khả năng tuyệt vời này, đã đến lúc đơn giản hóa phương pháp ExposeProperty. Đánh bóng tay nắm cửa là công việc nguy hiểm ...

Cảm ơn mọi người.


9
Nó thực sự đánh giá cao rằng bạn đã thêm giải pháp của bạn và gắn kết mọi thứ.
Đơn giản là G.


Bạn nên thêm giải pháp của mình làm câu trả lời - nó ngắn gọn hơn nhiều so với câu trả lời mà bạn chấp nhận.
Kenny Evitt

1
@Kenny Evitt: Xong :)
Jim C

Câu trả lời:


61

Sử dụng GetMemberInfo từ đây: Lấy tên thuộc tính từ biểu thức lambda bạn có thể làm một cái gì đó như thế này:

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

Điều đó thật tuyệt. Có vẻ như nó cũng sẽ hoạt động trên bất kỳ loại tài sản nào.
Jim C

Tôi chỉ thử nó với cả hai thuộc tính và thể hiện tĩnh. Càng xa càng tốt.
Jim C

Bất kỳ ý tưởng nơi tôi có thể nhận được gói lắp ráp hoặc NuGet có chứa GetMemberInfo? Tôi không thể tìm thấy bất cứ điều gì với gói 'tiện ích chung' cho Thư viện Microsoft Enterprise, đó là những gì MSDN dường như chỉ ra có chứa phương thức đó. Có một gói "không chính thức" nhưng không chính thức là không có lợi. Câu trả lời của JimC , dựa trên câu hỏi này, ngắn gọn hơn nhiều và không dựa vào một thư viện dường như không có.
Kenny Evitt

1
@KennyEvitt, phương pháp mà anh ấy tham khảo là một tác giả được viết bởi tác giả của câu hỏi mà anh ấy liên kết. Thay thế cho methodyou đó có thể sử dụng Type.GetMembers msdn.microsoft.com/en-us/l
Bon

464

Với C # 6.0, đây không phải là vấn đề như bạn có thể làm:

nameof(SomeProperty)

Biểu thức này được giải quyết tại thời gian biên dịch đến "SomeProperty".

Tài liệu MSDN của nameof .


18
Điều này là xấu và rất hữu ích cho các cuộc gọi ModelState.AddModelError.
Michael Silver

9
Và đây là một const string! Thật tuyệt vời
Jack

4
@RaidenCore chắc chắn, nếu bạn đang viết cho vi điều khiển, bạn nên sử dụng ngôn ngữ cấp thấp như C và nếu bạn cần siết chặt mọi bit hiệu suất như xử lý hình ảnh và video, bạn nên sử dụng C hoặc C ++. nhưng đối với 95% ứng dụng khác, khung mã được quản lý sẽ đủ nhanh. Cuối cùng, C # cũng được biên dịch thành mã máy và thậm chí bạn có thể biên dịch trước thành bản gốc nếu muốn.
Tsahi Asher

2
Nhân tiện, @RaidenCore, ứng dụng bạn đã đề cập trước C #, đó là lý do tại sao chúng được viết bằng C ++. Nếu họ được viết ngày hôm nay, ai biết ngôn ngữ nào đã được sử dụng. Xem ví dụ Paint.NET.
Tsahi Asher

1
Điều này thực sự hữu ích khi bạn muốn RaisePropertytrong WPF! Sử dụng RaisePropertyChanged (nameof (property)) thay vì RaisePropertyChanged ("property")
Pierre

17

Có một hack nổi tiếng để trích xuất nó từ biểu thức lambda (đây là từ lớp PropertyObserver, bởi Josh Smith, trong nền tảng MVVM của anh ấy):

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

Xin lỗi, điều này đã thiếu một số bối cảnh. Đây là một phần của lớp lớn hơn trong đó TPropertySourcelớp chứa thuộc tính. Bạn có thể tạo hàm chung trong TPropertySource để trích xuất nó từ lớp. Tôi khuyên bạn nên xem mã đầy đủ từ Quỹ MVVM .


Với một ví dụ về cách gọi hàm, đây chắc chắn là +1. Rất tiếc, đã không thấy rằng có một trong xác nhận gỡ lỗi - đó là lý do tại sao làm cho nhà phát triển cuộn theo chiều ngang để đến phần quan trọng của dòng là xấu;)
OregonGhost

Hmmm ... tôi cần phải mổ xẻ cái này để hiểu nó.
Jim C

Visual Studio 2008 đánh dấu "TPropertySource" là lỗi ("không thể tìm thấy").
Jim C

Tôi mới nhận ra tên loại của nó không chỉ là ký hiệu <T> như trong C ++. TPropertySource đại diện cho cái gì?
Jim C

2
Để thực hiện biên dịch này, bạn chỉ cần thay đổi chữ ký phương thức để đọc public static string GetPropertyName<TPropertySource>(Expression<Func<TPropertySource, object>> expression)rồi gọi như vậy:var name = GetPropertyName<TestClass>(x => x.Foo);
dav_i

16

Được rồi, đây là những gì tôi đã tạo ra (dựa trên câu trả lời tôi đã chọn và câu hỏi mà anh ấy tham khảo):

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

Sử dụng:

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

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

8

Lớp PropertyInfo sẽ giúp bạn đạt được điều này, nếu tôi hiểu chính xác.

  1. Phương thức Type.GetProperIES ()

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

Đây có phải là những gì bạn cần?


Không, mặc dù tôi sử dụng GetProperives khi ứng dụng nhận được yêu cầu "Một số điều". Ứng dụng tìm kiếm "someSecret" trong bản đồ để khám phá ứng dụng cần tìm một thuộc tính có tên "someProperty" trong một lớp có tên "someClass".
Jim C

nameof (someProperty) thực sự giảm bớt điều này từ .net 4.0 trở đi. Không cần hack dài như vậy.
Div Tiwari

6

Bạn có thể sử dụng Reflection để có được tên thực của các thuộc tính.

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

Nếu bạn cần một cách để gán "Tên chuỗi" cho một thuộc tính, tại sao bạn không viết một thuộc tính mà bạn có thể phản ánh để lấy tên chuỗi?

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

1
Ya, đó là cách ứng dụng xử lý các yêu cầu đến cho "someSecret", nhưng nó không cung cấp cho tôi một công cụ cho vấn đề ExposeProperty.
Jim C

Thật thú vị ... sau đó bạn có thể đổi tên MyProperty thành nội dung trái tim của mình miễn là bạn không gây rối với MyStringName và nếu vì lý do nào đó bạn muốn thay đổi nó thì bạn cần phải sửa đổi trang ExposeProperty. Ít nhất tôi có thể thêm một nhận xét bên cạnh thuộc tính với cảnh báo như vậy vì bạn phải xem nó để thay đổi giá trị của thuộc tính (không giống như đổi tên một thuộc tính, có thể được thực hiện từ bất kỳ vị trí tham chiếu nào).
Jim C

6

Tôi đã sửa đổi giải pháp của bạn để xâu chuỗi nhiều thuộc tính:

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

Sử dụng:

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

4

Dựa trên câu trả lời đã có trong câu hỏi và trên bài viết này: https://handccraftman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/ I đang trình bày giải pháp của tôi cho vấn đề này:

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

Và một bài kiểm tra cũng cho thấy việc sử dụng các thuộc tính tĩnh và ví dụ:

[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

Câu hỏi cũ, nhưng một câu trả lời khác cho câu hỏi này là tạo một hàm tĩnh trong lớp trình trợ giúp sử dụng CallerMemberNameAttribution.

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

Và sau đó sử dụng nó như:

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

0

Bạn có thể sử dụng lớp StackTrace để lấy tên của hàm hiện tại, (hoặc nếu bạn đặt mã trong một hàm, sau đó bước xuống một mức và nhận chức năng gọi).

Xem http://msdn.microsoft.com/en-us/l Library / system.diagnostics.stacktrace (VS.71) .aspx


Tôi không biết bạn đã ở đâu trong tâm trí để ghi lại dấu vết ngăn xếp, nhưng tôi không thể nghĩ ra cái nào có chứa tên của tài sản.
Jim C

Bạn có thể làm điều này, nhưng điều này có thể dẫn đến kết quả không mong muốn (bao gồm các trường hợp ngoại lệ) do tối ưu hóa trình biên dịch. smelser.net/blog/post/2008/11/27/ Mạnh
JoeGeeky

0

Tôi đã sử dụng câu trả lời này cho hiệu quả tuyệt vời: Lấy thuộc tính, dưới dạng chuỗi, từ Biểu thức <Func <TModel, TProperty >>

Tôi nhận ra tôi đã trả lời câu hỏi này một thời gian trước. Ưu điểm duy nhất mà câu trả lời khác của tôi có là nó hoạt động cho các thuộc tính tĩnh. Tôi thấy cú pháp trong câu trả lời này hữu ích hơn nhiều vì bạn không phải tạo một biến loại bạn muốn phản ánh.


0

Tôi gặp một số khó khăn khi sử dụng các giải pháp đã được đề xuất cho trường hợp sử dụng cụ thể của mình, nhưng cuối cùng đã tìm ra nó. Tôi không nghĩ trường hợp cụ thể của tôi xứng đáng với một câu hỏi mới, vì vậy tôi sẽ đăng giải pháp của mình lên đây để tham khảo. (Điều này liên quan rất chặt chẽ đến câu hỏi và cung cấp giải pháp cho bất kỳ ai khác có trường hợp tương tự với tôi).

Mã tôi đã kết thúc với hình như thế này:

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

Phần quan trọng đối với tôi là trong CompanyControllớp, trình biên dịch sẽ chỉ cho phép tôi chọn một thuộc tính boolean ICompanyViewModelgiúp các nhà phát triển khác dễ dàng làm cho nó đúng hơn.

Sự khác biệt chính giữa giải pháp của tôi và câu trả lời được chấp nhận là lớp của tôi là chung chung và tôi chỉ muốn khớp các thuộc tính từ loại chung là boolean.


0

Đó là cách tôi triển khai nó, lý do đằng sau là nếu lớp mà bạn muốn lấy tên từ thành viên của nó không tĩnh thì bạn cần tạo một bản năng của nó và sau đó lấy tên của thành viên. rất chung chung ở đây để giúp đỡ

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

cách sử dụng là như thế này

var label = ClassExtension.GetName<SomeClass>(x => x.Label); //x is refering to 'SomeClass'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.