Как получить значение свойства объекта по его имени (строке) через рефлексию в C#?
Основное решение: получаем значение свойства через рефлексию
Ваш инстинкт верен — нужно использовать GetProperty и затем GetValue. Вот полный рабочий метод с обработкой ошибок:
using System;
using System.Reflection;
public static object GetPropertyValue(object obj, string propertyName)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
if (string.IsNullOrWhiteSpace(propertyName))
throw new ArgumentException("Имя свойства не может быть пустым", nameof(propertyName));
// Получаем тип объекта
Type type = obj.GetType();
// Ищем свойство (по умолчанию — с учётом регистра, Public и Instance)
PropertyInfo property = type.GetProperty(propertyName);
if (property == null)
{
// Свойство не найдено — можно вернуть null или выбросить исключение
// throw new ArgumentException($"Свойство '{propertyName}' не найдено в типе {type.Name}");
return null; // Вариант: молча возвращаем null
}
// Проверяем, есть ли у свойства геттер
if (!property.CanRead)
throw new InvalidOperationException($"Свойство '{propertyName}' не имеет геттера");
// Получаем значение
return property.GetValue(obj);
}
// Пример использования:
Person person = new Person { Name = "John", Age = 30, Email = "john@example.com" };
object nameValue = GetPropertyValue(person, "Name"); // "John"
object ageValue = GetPropertyValue(person, "Age"); // 30 (boxed as object)
object missingValue = GetPropertyValue(person, "Address"); // null
Важные нюансы
- Регистрозависимость:
GetPropertyпо умолчанию чувствителен к регистру. Если нужно игнорировать регистр, используйте флагBindingFlags.IgnoreCase:PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); - Типы-значения: Если свойство возвращает
int,doubleи т.п., результат упаковывается (boxing) вobject. Чтобы получить его обратно, нужно распаковать:int age = (int)GetPropertyValue(person, "Age"); - Производительность: Рефлексия медленнее прямого доступа. Если метод вызывается часто (сотни тысяч раз), рассмотрите кэширование
PropertyInfoв словаре или использованиеDelegate.CreateDelegateдля создания типизированного делегата.
Расширенная версия с кэшированием
private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _cache =
new Dictionary<Type, Dictionary<string, PropertyInfo>>();
public static object GetPropertyValueCached(object obj, string propertyName)
{
Type type = obj.GetType();
if (!_cache.TryGetValue(type, out var propertyDict))
{
propertyDict = new Dictionary<string, PropertyInfo>();
_cache[type] = propertyDict;
}
if (!propertyDict.TryGetValue(propertyName, out var property))
{
property = type.GetProperty(propertyName);
propertyDict[propertyName] = property;
}
return property?.GetValue(obj);
}
Современный подход (C# 9+)
Если вы используете .NET 5+, можно упростить код с помощью required и record, но для рефлексии это не меняет логики. Главное — помните про BindingFlags для непубличных свойств:
// Для получения private свойств:
PropertyInfo privateProp = type.GetProperty("PrivateProperty",
BindingFlags.NonPublic | BindingFlags.Instance);
<<Альтернативный подход: использование динамических типов (ExpandoObject)
Если у вас нет строгой типизации и вы работаете с динамическими данными (например, JSON), можно избежать рефлексии, используя ExpandoObject и приведение к IDictionary<string, object>:
using System.Dynamic;
dynamic person = new ExpandoObject();
person.Name = "John";
person.Age = 30;
// Получаем значение по строке
var dict = (IDictionary<string, object>)person;
object name = dict["Name"]; // "John"
object age = dict["Age"]; // 30
Когда это полезно?
- Когда структура объекта заранее неизвестна (например, при десериализации JSON без модели).
- Когда нужно часто менять набор свойств.
- Когда производительность не критична (рефлексия иногда быстрее, но
ExpandoObjectудобнее).
Недостатки
- Потеря строгой типизации — ошибки в именах свойств обнаружатся только в рантайме.
- Не подходит для существующих классов вроде
Person— их нельзя просто так привести кExpandoObject.
Для вашей задачи с классом Person рефлексия — правильный выбор. Но если вы когда-нибудь будете работать с динамическими данными, имейте в виду этот вариант.