Uzyskaj nazwę właściwości jako ciąg

203

(Zobacz poniżej rozwiązanie, które utworzyłem przy użyciu odpowiedzi, którą zaakceptowałem)

Usiłuję poprawić konserwowalność kodu wymagającego refleksji. Aplikacja ma interfejs zdalny .NET, odsłaniając (między innymi) metodę o nazwie Wykonaj, aby uzyskać dostęp do części aplikacji nie zawartych w opublikowanym zdalnym interfejsie.

Oto, w jaki sposób aplikacja określa właściwości (w tym przykładzie statyczne), które mają być dostępne za pośrednictwem polecenia Wykonaj:

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

Aby użytkownik zdalny mógł zadzwonić:

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

a aplikacja użyłaby odbicia, aby znaleźć SomeClass.SomeProperty i zwrócić jego wartość jako ciąg.

Niestety, jeśli ktoś zmieni nazwę SomeProperty i zapomni zmienić trzecią parmę ExposeProperty (), zepsuje ten mechanizm.

Potrzebuję odpowiednika:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

do użycia jako 3. parma w ExposeProperty, aby narzędzia do refaktoryzacji zajęłyby się nazwami.

Czy jest na to sposób? Z góry dziękuję.

Okej, oto co ostatecznie stworzyłem (na podstawie wybranej przeze mnie odpowiedzi i pytania, do którego się odwołał):

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

Stosowanie:

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

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

Teraz dzięki tej fajnej funkcji czas uprościć metodę ExposeProperty. Polerowanie klamek to niebezpieczna praca ...

Dziękuję wszystkim.

Jim C.
źródło
9
Naprawdę doceniło to, że dodałeś swoje rozwiązanie i związałeś wszystko.
Po prostu G.
Powinieneś dodać swoje rozwiązanie jako odpowiedź - jest ono o wiele bardziej zwięzłe niż odpowiedź, którą zaakceptowałeś.
Kenny Evitt
1
@Kenny Evitt: Gotowe:)
Jim C
@JimC Upvoted! I link w komentarzu do obecnie akceptowanej odpowiedzi . Dzięki!
Kenny Evitt

Odpowiedzi:

61

Za pomocą GetMemberInfo stąd: Pobieranie nazwy właściwości z wyrażenia lambda możesz zrobić coś takiego:

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);
    }
}
Daniel Renshaw
źródło
To jest całkowicie fajne. Wygląda na to, że działałoby to również na każdym typie właściwości.
Jim C,
Właśnie próbowałem z właściwościami instancji i statycznymi. Na razie w porządku.
Jim C,
Masz pomysł, gdzie mogę uzyskać zestaw lub pakiet NuGet, który zawiera GetMemberInfo? Nic nie mogę znaleźć w pakiecie „wspólnych narzędzi” dla biblioteki Microsoft Enterprise Library, co sugeruje, że MSDN zawiera tę metodę. Istnieje pakiet „nieoficjalny”, ale bycie nieoficjalnym jest mało inspirujące. Odpowiedź JimC , która jest oparta na tej, jest znacznie bardziej zwięzła i nie opiera się na pozornie niedostępnej bibliotece.
Kenny Evitt
1
@KennyEvitt, metoda, do której się odwołuje, została napisana przez autora pytania, do którego się odnosił. Alternatywą dla tej metody jest użycie tego Type.GetMembers msdn.microsoft.com/en-us/library/...
Bon
464

W wersji C # 6.0 nie jest to problem, ponieważ możesz:

nameof(SomeProperty)

To wyrażenie jest rozwiązywane w czasie kompilacji do "SomeProperty".

Dokumentacja MSDN nazwy nameof .

James Ko
źródło
18
Jest to nieprzyzwoite i bardzo przydatne w przypadku wywołań ModelState.AddModelError.
Michael Silver,
9
A to jest const string! Niesamowite
Jack
4
@RaidenCzy pamiętaj, że jeśli piszesz dla mikrokontrolera, powinieneś używać języka niskiego poziomu, takiego jak C, a jeśli potrzebujesz wycisnąć każdą wydajność, taką jak przetwarzanie obrazu i wideo, powinieneś użyć C lub C ++. ale dla pozostałych 95% aplikacji struktura kodu zarządzanego będzie wystarczająco szybka. Ostatecznie C # jest również kompilowany do kodu maszynowego, a nawet możesz go wstępnie skompilować do natywnego, jeśli chcesz.
Tsahi Asher
2
Nawiasem mówiąc, @RaidenCore, aplikacje, o których wspominałeś wcześniej niż C #, dlatego są napisane w C ++. Gdyby zostały napisane dzisiaj, kto wie, jakiego języka użyto. Zobacz np. Paint.NET.
Tsahi Asher
1
Jest to bardzo przydatne, gdy chcesz RaisePropertyw WPF! Użyj RaisePropertyChanged (nameof (właściwość)) zamiast RaisePropertyChanged („właściwość”)
Pierre
17

Istnieje dobrze znany hack do wyodrębnienia go z wyrażenia lambda (pochodzi z klasy PropertyObserver, autorstwa Josh Smith, w jego fundacji 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;
    }

Przepraszamy, brakowało jakiegoś kontekstu. Była to część większej klasy, w której TPropertySourceznajduje się klasa zawierająca właściwość. Możesz ustawić funkcję rodzajową w TPropertySource, aby wyodrębnić ją z klasy. Polecam zajrzeć do pełnego kodu z MVVM Foundation .

Dan Bryant
źródło
Na przykładzie wywołania funkcji jest to z pewnością +1. Ups, nie widziałem, że jest jedno w asercji debugowania - dlatego zmuszanie programisty do przewijania w poziomie, aby dostać się do ważnej części linii, jest złe;)
OregonGhost
Hmmm ... Muszę to przeanalizować, żeby to zrozumieć.
Jim C
Visual Studio 2008 oznacza „TPropertySource” jako błąd („nie można znaleźć”).
Jim C,
Właśnie zdałem sobie sprawę, że jest to nazwa typu, a nie tylko symbol <T>, jak w C ++. Co reprezentuje TPropertySource?
Jim C,
2
Aby skompilować ten kompilator, wystarczy zmienić sygnaturę metody na odczyt, public static string GetPropertyName<TPropertySource>(Expression<Func<TPropertySource, object>> expression)a następnie wywołać tak:var name = GetPropertyName<TestClass>(x => x.Foo);
dav_i
16

Okej, oto co ostatecznie stworzyłem (na podstawie wybranej przeze mnie odpowiedzi i pytania, do którego się odwołał):

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

Stosowanie:

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

// Instance Property
string name = GetPropertyName(() => someObject.SomeProperty);
Jim C.
źródło
8

PropertyInfo klasa powinna pomóc w osiągnięciu tego, czy dobrze rozumiem.

  1. Metoda Type.GetProperties ()

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

Czy tego potrzebujesz?

Will Marcouiller
źródło
Nie, chociaż używam GetProperties, gdy aplikacja otrzyma żądanie „SomeSecret”. Aplikacja wyszukuje „SomeSecret” na mapie, aby odkryć, że musi znaleźć właściwość o nazwie „SomeProperty” w klasie o nazwie „SomeClass”.
Jim C,
nameof (SomeProperty) faktycznie ułatwia to od wersji .net 4.0. Nie potrzeba tak długich hacków.
Div Tiwari
6

Możesz użyć Odbicia, aby uzyskać rzeczywiste nazwy właściwości.

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

Jeśli potrzebujesz sposobu na przypisanie właściwości „String Name” do właściwości, dlaczego nie napiszesz atrybutu, nad którym możesz się zastanowić, aby uzyskać nazwę string?

[StringName("MyStringName")]
private string MyProperty
{
    get { ... }
}
Robert Harvey
źródło
1
Tak, w ten sposób aplikacja obsługuje przychodzące żądania „SomeSecret”, ale nie daje mi narzędzia do rozwiązania problemu ExposeProperty.
Jim C,
Ciekawe ... to możesz zmienić nazwę MyProperty na swoje serca, o ile nie zadzierasz z MyStringName, a jeśli z jakiegoś powodu chcesz to zmienić, musisz zmodyfikować parametr ExposeProperty. Przynajmniej mógłbym dodać komentarz do atrybutu z takim ostrzeżeniem, ponieważ musisz na niego patrzeć, aby zmienić wartość atrybutu (w przeciwieństwie do zmiany nazwy właściwości, co można zrobić z dowolnej lokalizacji odniesienia).
Jim C
6

Zmodyfikowałem twoje rozwiązanie, aby połączyć je w wiele właściwości:

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

Stosowanie:

string name = GetPropertyName(() => someObject.SomeProperty.SomeOtherProperty);
// returns "SomeProperty.SomeOtherProperty"
hypehuman
źródło
4

Na podstawie odpowiedzi, która jest już zawarta w pytaniu oraz w tym artykule: https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-with-magic-strings/ I przedstawiam moje rozwiązanie tego problemu:

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

I Test, który pokazuje również użycie instancji i właściwości statycznych:

[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));
    }
}
Tomasz
źródło
3

Stare pytanie, ale inną odpowiedzią na to pytanie jest utworzenie funkcji statycznej w klasie pomocniczej, która korzysta z CallerMemberNameAttribute.

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

A następnie użyj go w następujący sposób:

public string MyProperty {
  get { Console.WriteLine("{0} was called", GetPropertyName()); return _myProperty; }
}
Jim Pedid
źródło
0

Możesz użyć klasy StackTrace, aby uzyskać nazwę bieżącej funkcji (lub jeśli umieścisz kod w funkcji, a następnie zejdź poziom niżej i uzyskaj funkcję wywołującą).

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

Sprotty
źródło
Nie wiem, gdzie miałeś na myśli przechwycenie śladu stosu, ale nie mogę wymyślić takiego, który zawierałby nazwę właściwości.
Jim C,
Możesz to zrobić, ale może to prowadzić do nieoczekiwanych wyników (w tym wyjątków) ze względu na optymalizacje wbudowane w kompilator. smelser.net/blog/post/2008/11/27/…
JoeGeeky
0

Użyłem tej odpowiedzi z wielkim efektem: pobierz właściwość jako ciąg znaków z wyrażenia <Func <TModel, TProperty >>

Zdaję sobie sprawę, że już dawno odpowiedziałem na to pytanie. Jedyną zaletą mojej drugiej odpowiedzi jest to, że działa dla właściwości statycznych. Uważam, że składnia w tej odpowiedzi jest o wiele bardziej przydatna, ponieważ nie musisz tworzyć zmiennej typu, który chcesz odzwierciedlić.

hypehuman
źródło
0

Miałem pewne trudności z użyciem rozwiązań już sugerowanych dla mojego konkretnego przypadku użycia, ale w końcu to wymyśliłem. Nie sądzę, aby mój konkretny przypadek był wart nowego pytania, dlatego zamieszczam tutaj swoje rozwiązanie w celach informacyjnych. (Jest to bardzo ściśle związane z pytaniem i zapewnia rozwiązanie dla każdego, kto ma podobny przypadek do mojego).

Kod, na którym skończyłem, wygląda następująco:

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

Ważną częścią dla mnie jest to, że w CompanyControlklasie kompilator pozwoli mi tylko wybrać właściwość boolean, ICompanyViewModelktórej ułatwienie innym programistom jest prawidłowe.

Główną różnicą między moim rozwiązaniem a zaakceptowaną odpowiedzią jest to, że moja klasa jest ogólna i chcę dopasować właściwości tylko z typu ogólnego, które są boolowskie.

bikeman868
źródło
0

tak to zaimplementowałem. Powodem jest to, że jeśli klasa, która ma otrzymać nazwę od swojego członka, nie jest statyczna, musisz utworzyć instancję tego, a następnie uzyskać nazwę członka. więc ogólna pomoc przychodzi tutaj

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

użycie jest takie

var label = ClassExtension.GetName<SomeClass>(x => x.Label); //x is refering to 'SomeClass'
Mo Hrad A.
źródło