Отримайте назву властивості у вигляді рядка


203

(Дивіться нижче рішення, яке я створив, використовуючи відповідь, яку я прийняв)

Я намагаюся покращити ремонтопридатність якогось коду, що включає рефлексію. У додатку є інтерфейс видалення .NET, що відкриває (серед іншого) метод, який називається Execute для доступу до частин програми, які не входять до його опублікованого віддаленого інтерфейсу.

Ось як додаток позначає властивості (статичні в цьому прикладі), які повинні бути доступні через Execute:

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

Тож віддалений користувач може зателефонувати:

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

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

На жаль, якщо хтось перейменує SomeProperty і забуде змінити 3-ю парму ExposeProperty (), він порушує цей механізм.

Мені потрібно еквівалент:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

використовувати в якості 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
Це дійсно зрозуміло, що ви додали рішення та зв’язали речі.
Просто Г.


Ви повинні додати своє рішення як відповідь - це набагато більш стисло, ніж прийнята відповідь.
Кенні Евітт

1
@Kenny Evitt: Готово:)
Джим C

@JimC Оновлено! І пов'язане в коментарі до прийнятої відповіді . Дякую!
Кенні Евітт

Відповіді:


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

Це абсолютно круто. Схоже, це також буде працювати на будь-якому типі властивостей.
Джим C

Я просто спробував це як з екземплярами, так і статичними властивостями. Все йде нормально.
Джим C

Будь-яка ідея, де я можу отримати збірку або пакет NuGet, який містить GetMemberInfo? Я не можу знайти нічого з пакетом "загальних утиліт" для бібліотеки Microsoft Enterprise, який, як видається, вказує MSDN, містить цей метод. Є "неофіційний" пакет, але бути неофіційним - це не надихає. Відповідь JimC , що ґрунтується на цій, набагато більш стислий і не покладається на, здавалося б, недоступну бібліотеку.
Кенні Евітт

1
@KennyEvitt, метод, на який він посилається, - це написаний автором пов’язаного з ним питання. Альтернатива , що methodyou може використовувати цей Type.GetMembers msdn.microsoft.com/en-us/library / ...
Bon

464

Що стосується C # 6.0, це вже не проблема, як це можна зробити:

nameof(SomeProperty)

Цей вираз вирішується під час компіляції до "SomeProperty".

Документація MSDN іменіof .


18
Це погано та дуже корисно для викликів ModelState.AddModelError.
Майкл Сілвер

9
І це const string! Дивовижно
Джек

4
@RaidenВпевнений, що якщо ви пишете для мікроконтролера, ви повинні використовувати мову низького рівня, як C, і якщо вам потрібно стиснути кожен шматочок продуктивності, як обробка зображень і відео, ви повинні використовувати C або C ++. але для інших 95% застосунків керована структура коду буде досить швидкою. Врешті-решт C # також компілюється до машинного коду, і ви навіть можете попередньо скласти його до рідного, якщо хочете.
Цахі Ашер

2
До речі, @RaidenCore, згадані вами програми передували C #, тому вони написані на C ++. Якби вони були написані сьогодні, хто знає, якою мовою користувався. Див., Наприклад, Paint.NET.
Цахі Ашер

1
Це дійсно корисно, коли ви хочете RaisePropertyв WPF! Використовуйте RaisePropertyChanged (nameof (властивість)) замість RaisePropertyChanged ("властивість")
Pierre

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 .


На прикладі того, як викликати функцію, це, звичайно, +1. До жаль, не бачив , що є один в отладочном затвердження - ось чому робить розробник горизонтально прокрутки , щоб дістатися до важливої частини лінії є зло;)
OregonGhost

Хммм ... мені потрібно розсікати цю, щоб зрозуміти це.
Jim C

Visual Studio 2008 позначає "TPropertySource" як помилку ("неможливо знайти").
Jim C

Я щойно зрозумів, що це ім'я типу не просто символ <T>, як у C ++. Що являє собою TPropertySource?
Джим 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));
    

Це те, що вам потрібно?


Ні, хоча я використовую GetProperties, коли додаток отримує запит на "SomeSecret". Додаток шукає "SomeSecret" на карті, щоб виявити, що потрібно знайти властивість під назвою "SomeProperty" в класі під назвою "SomeClass".
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

Цікаво ... тоді ви можете перейменувати MyProperty на вміст ваших сердець до тих пір, поки ви не заплутаєтесь з MyStringName, і якщо з якоїсь причини ви хочете змінити його, вам потрібно змінити парм ExposeProperty. Принаймні, я можу додати коментар поруч із атрибутом із таким попередженням, оскільки вам потрібно буде дивитися на нього, щоб змінити значення атрибута (на відміну від перейменування властивості, що можна зробити з будь-якого довідкового місця).
Джим 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


Я не знаю, де ви мали намір записати слід стека, але я не можу придумати той, який би містив назву ресурсу.
Джим C

Це можна зробити, але це може призвести до несподіваних результатів (включаючи винятки) через оптимізацію компілятора. smelser.net/blog/post/2008/11/27/…
JoeGeeky

0

Я використовую цю відповідь з великим ефектом: Отримайте властивість у вигляді рядка з виразу <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'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.