Строгий ввод имени свойства в.NET

Скажем, у меня есть класс с одним свойством

Public Class MyClass
   Public Property MyItem() as Object
      ....
   End Property
End Class

Я должен передать имя свойства вызову функции. (Пожалуйста, не спрашивайте, почему это должно быть сделано таким образом, это сторонние рамки). Например

SomeFunc("MyItem")

Но я бы хотел изменить строку на строго типизированный параметр. То есть, если имя свойства переименовано или изменено, оно должно быть отражено и здесь.

Итак, что-то типа этого:

Dim objectForStrongTyping as New MyClass()
SomeFunc(objectForStrongTyping.MyItem().Name())

Я уверен, что это не сработает. Есть ли способ, которым это сильное печатание может быть сделано? (C# или VB.NET, любая вещь крутая)

6 ответов

Решение

Вот решение с использованием классов из System.Linq.Expressions,

static MemberInfo GetMemberInfo<TObject, TProperty>(
    Expression<Func<TObject, TProperty>> expression
) {
    var member = expression.Body as MemberExpression;
    if (member != null) {
        return member.Member;
    }

    throw new ArgumentException("expression");
}

Просто брось это в класс где-нибудь (ExpressionHelper?).

Использование:

class SomeClass {
    public string SomeProperty { get; set; }
}

MemberInfo member = GetMemberInfo((SomeClass s) => s.SomeProperty);
Console.WriteLine(member.Name); // prints "SomeProperty" on the console

В C# 6.0 появилась новая функция под названием nameof. В основном вы можете сделать это:

var name = nameof(MyClass.MyItem);

Глядя на конвертер кода Telerik из C# в VB, кажется, что это эквивалент VB:

Dim name = nameof([MyClass].MyItem)

Таким образом, вы можете сделать следующее:

SomeFunc(nameof(MyClass.MyItem));

Вот ссылка на документацию Microsoft: https://docs.microsoft.com/en-us/dotnet/articles/csharp/language-reference/keywords/nameof

Это решение работает как в C#, так и в VB.NET, но синтаксис VB.NET для лямбда-функций не так чист, что, вероятно, сделает это решение менее привлекательным в VB. Мои примеры будут в C#.

Вы можете добиться желаемого эффекта, используя лямбда-функцию и функции дерева выражений C# 3. По сути, вы бы написали функцию-обертку с именем SomeFuncHelper и вызвали бы ее так:

MyClass objForStrongTyping = new MyClass();
SomeFuncHelper(() => objForStrongTyping.MyItem);

SomeFuncHelper реализован следующим образом:

void SomeFuncHelper(Expression<Func<object>> expression)
{
    string propertyName = /* get name by examining expression */;
    SomeFunc(propertyName);
}

Лямбда-выражение () => objForStrongTyping.MyItem переводится в объект Expression, который передается SomeFuncHelper. SomeFuncHelper проверяет выражение, извлекает имя свойства и вызывает SomeFunc. В моем быстром тесте следующий код работает для извлечения имени свойства, предполагая, что SomeFuncHelper всегда вызывается, как показано выше (т.е. () => someObject.SomeProperty):

propertyName = ((MemberExpression) ((UnaryExpression) expression.Body).Operand).Member.Name;

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

Обновление: это похоже на решение Джейсона, но позволяет лямбда-выражению внутри вызова вспомогательной функции быть немного проще (() => obj.Property вместо (SomeType obj) => obj.Property). Конечно, это проще, если у вас уже есть экземпляр типа сидящий без дела.

Если есть только одно свойство, вы можете сделать это - получите информацию о свойствах первого свойства класса:

//C# syntax
typeof(MyClass).GetProperties()[0].Name;

'VB syntax
GetType(MyClass).GetProperties()(0).Name

РЕДАКТИРОВАТЬ Оказывается, где вы можете использовать выражения, вы также можете использовать проекцию для такого рода отражения (код C#).

public static class ObjectExtensions {
    public static string GetVariableName<T>(this T obj) {
        System.Reflection.PropertyInfo[] objGetTypeGetProperties = obj.GetType().GetProperties();

        if(objGetTypeGetProperties.Length == 1)
            return objGetTypeGetProperties[0].Name;
        else
            throw new ArgumentException("object must contain one property");
    }
}

class Program {
    static void Main(string[] args) {
        Console.WriteLine(Console.WriteLine(new { (new MyClass()).MyItem}.GetVariableName()););
    }
}

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

Лучшее решение, которое я думаю, - генерировать статические константы с использованием T4 (например, T4MVC).

public static class StaticSampleClass
{
    public const string MyProperty = "MyProperty";
}

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

Плохо то, что T4 ушел в чистое ядро.:(

Хорошая вещь в C#6.0 вы можете использовать nameof(SampleClass.MyProperty)

В худшем случае вы можете использовать следующий пример:

using System.Linq.Expressions;

namespace ConsoleApp1
{
    public static class Helper
    {
        public static string GetPropertyName<T>(Expression<Func<T, object>> propertyExpression)
        {
            var member = propertyExpression.Body as MemberExpression;
            if (member != null)
                return member.Member.Name;
            else
                throw new ArgumentNullException("Property name not found.");
        }
        public static string GetPropertyName<T>(this T obj, Expression<Func<T, object>> propertyExpression)
        {
            return GetPropertyName(propertyExpression);
        }
    }

    public class SampleClass
    {
        public string MyProperty { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Property name of type
            Console.WriteLine(Helper.GetPropertyName<SampleClass>(x => x.MyProperty));

            // Property name of instance
            var someObject = new SampleClass();
            Console.WriteLine(someObject.GetPropertyName(x => x.MyProperty));

            Console.ReadKey();
        }
    }
}

Результаты производительности (1 миллион звонков):

StaticSampleClass.MyProperty - 8 мс

nameof(SampleClass.MyProperty) - 8 мс

Helper.GetPropertyName<SampleClass>(x => x.MyProperty) - 2000 мс

Вы всегда можете использовать статический класс, который содержит string константы вместо передачи в string буквальный:

public static class ObjectForStrongTyping
{
    public const string MyItem = "MyItem";
    public const string MyOtherItem = "MyOtherItem";
    // ...
}

Ваш код станет:

SomeFunc(ObjectForStrongTyping.MyItem);
Другие вопросы по тегам