Получить имя свойства в виде строки

204

(См. Ниже решение, которое я создал, используя ответ, который я принял)

Я пытаюсь улучшить удобство сопровождения некоторого кода с помощью отражения. Приложение имеет интерфейс .NET Remoting, который предоставляет (помимо прочего) метод Execute для доступа к частям приложения, не включенным в опубликованный удаленный интерфейс.

Вот как приложение определяет свойства (статические в этом примере), которые должны быть доступны через Execute:

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

Таким образом, удаленный пользователь может позвонить:

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

и приложение будет использовать отражение, чтобы найти SomeClass.SomeProperty и вернуть его значение в виде строки.

К сожалению, если кто-то переименует SomeProperty и забудет изменить третий параметр ExposeProperty (), это нарушит этот механизм.

Мне нужно эквивалент:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

использовать в качестве третьего параметра в 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
Очень приятно, что вы добавили свое решение и связали вещи.
Просто Г.
Вы должны добавить свое решение в качестве ответа - оно гораздо более краткое, чем ответ, который вы приняли.
Кенни Эвитт
1
@ Кенни Эвитт: Готово:)
Джим С
@JimC Upvoted! И ссылка в комментарии на принятый в настоящее время ответ . Спасибо!
Кенни Эвитт

Ответы:

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);
    }
}
Даниэль Реншоу
источник
Это совершенно круто. Похоже, это будет работать на любой тип свойства тоже.
Джим С
Я только что попробовал это и с экземпляром и со статическими свойствами. Все идет нормально.
Джим С
Любая идея, где я могу получить сборку или пакет NuGet, который содержит GetMemberInfo? Я не могу найти ничего с пакетом «общих утилит» для Microsoft Enterprise Library, который, как кажется, указывает MSDN, содержит этот метод. Есть "неофициальный" пакет, но быть неофициальным - скучно. Ответ JimC , основанный на этом, гораздо более лаконичен и не зависит от, казалось бы, недоступной библиотеки.
Кенни Эвитт
1
@KennyEvitt, метод, на который он ссылается, написан автором вопроса, который он связал. В качестве альтернативы этому методу вы можете использовать этот Type.GetMembers msdn.microsoft.com/en-us/library/…
Bon
464

С C # 6.0 это теперь не проблема, как вы можете сделать:

nameof(SomeProperty)

Это выражение разрешается во время компиляции в "SomeProperty".

MSDN документация имени .

Джеймс Ко
источник
18
Это круто и очень полезно для вызовов ModelState.AddModelError.
Майкл Сильвер
9
И это const string! Удивительно
Джек
4
@Raiden Не сомневаюсь, что если вы пишете для микроконтроллера, вам следует использовать язык низкого уровня, такой как C, а если вам нужно сжать каждый бит производительности, такой как обработка изображений и видео, вы должны использовать C или C ++. но для остальных 95% приложений структура управляемого кода будет достаточно быстрой. В конце концов C # также компилируется в машинный код, и вы можете даже предварительно скомпилировать его в native, если хотите.
Цахи Ашер
2
Кстати, @RaidenCore, приложения, о которых вы упоминали, предшествовали C #, поэтому они написаны на C ++. Если бы они были написаны сегодня, кто знает, какой язык использовался. Смотрите, например, Paint.NET.
Цахи Ашер
1
Это действительно полезно, когда вы хотите RaisePropertyв WPF! Используйте RaisePropertyChanged (nameof (property)) вместо RaisePropertyChanged ("property")
Пьер
17

Есть известный хак для извлечения его из лямбда-выражения (это из класса PropertyObserver Джоша Смита в его фундаменте 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 Foundation .

Дэн Брайант
источник
С примером того, как вызывать функцию, это, безусловно, +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 и далее. Нет необходимости в таких долгих взломах.
Див Тивари
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"
hypehuman
источник
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

Sprotty
источник
Я не знаю, где вы имели в виду захват трассировки стека, но я не могу вспомнить ту, которая содержала бы имя свойства.
Джим С
Вы можете сделать это, но это может привести к неожиданным результатам (включая исключения) из-за встроенных оптимизаций компилятора. smelser.net/blog/post/2008/11/27/…
JoeGeeky
0

Я использовал этот ответ, чтобы получить отличный эффект: получить свойство в виде строки из выражения <Func <TModel, TProperty >>

Я понимаю, что уже ответил на этот вопрос некоторое время назад. Единственное преимущество, которое дает другой мой ответ - это то, что он работает для статических свойств. Я нахожу синтаксис в этом ответе гораздо более полезным, потому что вам не нужно создавать переменную того типа, который вы хотите отразить.

hypehuman
источник
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 которое упрощает понимание другими разработчиками.

Основное различие между моим решением и принятым ответом состоит в том, что мой класс является универсальным, и я хочу сопоставлять только свойства из универсального типа, которые являются логическими.

bikeman868
источник
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'
Мо Град А
источник