문자열로 속성 이름 가져 오기


203

(수락 한 답변을 사용하여 만든 솔루션을 아래에서 참조하십시오)

리플렉션과 관련된 일부 코드의 유지 보수성을 개선하려고합니다. 이 앱에는 게시 된 원격 인터페이스에 포함되지 않은 앱의 일부에 액세스하기 위해 Execute라는 방법을 노출시키는 .NET Remoting 인터페이스가 있습니다.

다음은 앱이 Execute를 통해 액세스 할 수있는 속성 (이 예제에서는 정적 속성)을 지정하는 방법입니다.

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

따라서 원격 사용자는 다음을 호출 할 수 있습니다.

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

그리고 앱은 리플렉션을 사용하여 SomeClass.SomeProperty를 찾고 그 값을 문자열로 반환합니다.

불행히도 누군가 누군가 SomeProperty의 이름을 바꾸고 ExposeProperty ()의 세 번째 매개 변수를 변경하는 것을 잊어 버린 경우이 메커니즘을 손상시킵니다.

나는 다음과 동등해야합니다.

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

리팩토링 도구는 이름 변경을 처리 할 수 ​​있도록 ExposeProperty에서 3 번째 매개 변수로 사용합니다.

이 방법이 있습니까? 미리 감사드립니다.

좋아, 내가 선택한 결과는 다음과 같습니다 (선택한 답변과 그가 언급 한 질문에 따라).

// <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
그것은 당신이 당신의 솔루션을 추가하고 사물을 묶어 놓은 것에 실제로 적용되었습니다.
Simply G.


솔루션을 답변으로 추가해야합니다. 수락 한 답변보다 훨씬 간결합니다.
Kenny Evitt

1
@Kenny Evitt : Done :)
Jim C

@JimC Upvoted! 그리고 현재 받아 들여진 답변대한 의견에 링크되어 있습니다. 감사!
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

방금 인스턴스 속성과 정적 속성으로 시도했습니다. 여태까지는 그런대로 잘됐다.
Jim C

포함 된 어셈블리 또는 NuGet 패키지를 어디에서 얻을 수 GetMemberInfo있습니까? MSDN에 '공통 유틸리티'패키지가있는 MSDN을 찾을 수 없습니다 .MSDN에는 해당 방법이 포함되어 있습니다. "비공식"패키지가 있지만 비공식적 인 것은 고무적이지 않습니다. 이것에 기반한 JimC의 대답 은 훨씬 간결하며 사용 불가능 해 보이는 라이브러리에 의존하지 않습니다.
Kenny Evitt

1
@KennyEvitt, 그가 참조하는 방법은 그가 연결 한 질문의 저자가 작성한 방법입니다. 이 방법 대신이 Type.GetMembers msdn.microsoft.com/en-us/library/…를
Bon

464

C # 6.0을 사용하면 다음과 같이 할 수있는 문제가 아닙니다.

nameof(SomeProperty)

이 표현식은 컴파일 타임에로 해석됩니다 "SomeProperty".

nameof의 MSDN 설명서 .


18
이것은 잘못된 것이며 ModelState.AddModelError 호출에 매우 유용합니다.
Michael Silver

9
그리고 이것은 const string! 놀라운
Jack

4
@RaidenCore 확실히 마이크로 컨트롤러를 작성하는 경우 C와 같은 저수준 언어를 사용해야하며 이미지 및 비디오 처리와 같은 모든 성능 비트를 압축 해야하는 경우 C 또는 C ++을 사용해야합니다. 그러나 나머지 95 %의 응용 프로그램에서는 관리 코드 프레임 워크가 충분히 빠릅니다. 결국 C #은 기계 코드로 컴파일되며 원하는 경우 네이티브로 사전 컴파일 할 수도 있습니다.
Tsahi Asher

2
그건 그렇고, 언급 한 응용 프로그램 인 @RaidenCore는 C #보다 앞서 있으므로 C ++로 작성되었습니다. 그들이 오늘 쓰여졌다면, 누가 어떤 언어가 사용되는지 알고 있습니다. Paint.NET을 참조하십시오.
Tsahi Asher

1
이것은 RaisePropertyWPF에서 원할 때 정말 유용합니다 ! RaisePropertyChanged ( "property") 대신 RaisePropertyChanged (nameof (property))를 사용하십시오.
Pierre

17

람다 식에서 추출하는 잘 알려진 해킹이 있습니다 (MJVM의 Josh Smith가 PropertyObserver 클래스에서 가져온 것임).

    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 Foundation 에서 전체 코드를 살펴 보는 것이 좋습니다 .


함수를 호출하는 방법의 예를 들어, 이것은 확실히 +1입니다. 죄송합니다. 디버그 어설 션에 하나가 있다는 것을 보지 못했습니다. 따라서 개발자가 가로로 스크롤하여 줄의 중요한 부분에 도달하는 것은 악의적입니다.)
OregonGhost

흠 ... 이해하기 위해이 부분을 해부해야합니다.
Jim C

Visual Studio 2008은 "TPropertySource"를 오류 ( "찾을 수 없음")로 표시합니다.
Jim C

C ++ 에서처럼 <T> 기호가 아니라 형식 이름을 깨달았습니다. TPropertySource는 무엇을 나타 냅니까?
Jim C

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

이것이 당신이 필요한 것입니까?


아니요, 앱이 "SomeSecret"에 대한 요청을받을 때 GetProperties를 사용하지만 앱은 "SomeClass"라는 클래스에서 "SomeProperty"라는 속성을 찾아야한다는 것을 발견하기 위해지도에서 "SomeSecret"을 찾습니다.
Jim C

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 문제에 대한 도구는 제공하지 않습니다.
Jim C

흥미롭게도 MyStringName을 엉망으로하지 않는 한 MyProperty의 이름을 하트 컨텐츠로 바꿀 수 있으며 어떤 이유로 변경하려는 경우 ExposeProperty 매개 변수를 수정해야합니다. 적어도 속성의 값을 변경하려면 참조 위치에서 수행 할 수있는 속성의 이름을 바꾸는 것과 달리 속성 값을 변경해야하므로 경고와 함께 속성 옆에 주석을 추가 할 수 있습니다.
Jim C

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를 참조하십시오 .


스택 추적을 어디에서 캡처해야할지 모르겠지만 속성 이름이 포함 된 것을 생각할 수 없습니다.
Jim C

이 작업을 수행 할 수 있지만 컴파일러 인라인 최적화로 인해 예기치 않은 결과 (예외 포함)가 발생할 수 있습니다. smelser.net/blog/post/2008/11/27/…
JoeGeeky


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.