Строгий ввод имени свойства в.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);