Как получить значение свойства по имени через рефлексию в C#: полный гайд

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как получить значение свойства по имени через рефлексию в C#: разбор для начинающих

Представьте ситуацию: вы пишете код, в котором нужно динамически, на лету, обратиться к свойству объекта. Имя этого свойства приходит, например, из конфигурационного файла, из JSON-запроса или вводится пользователем. Жёстко прописать object.PropertyName нельзя — вы не знаете имя заранее. Как быть?

На помощь приходит рефлексия (reflection) — мощный механизм C#, позволяющий исследовать типы данных и их члены (свойства, методы, поля) во время выполнения программы. В этой статье мы разберём самый популярный вопрос на Stack Overflow: как получить значение свойства объекта по строке, содержащей его имя. Разберём на реальных примерах, от простого к сложному, и рассмотрим частые грабли, на которые наступают новички.

Что такое рефлексия и зачем она нужна?

Рефлексия в C# — это возможность программы "посмотреть на себя" и получить информацию о своих собственных типах. Пространство имён System.Reflection предоставляет классы вроде Type, PropertyInfo, MethodInfo, которые позволяют вам:

  • Узнать все свойства и методы класса.
  • Создавать экземпляры классов без оператора new.
  • Вызывать методы и получать/устанавливать значения свойств по их строковым именам.

Без рефлексии вы бы не смогли, например, написать универсальный маппер объектов (как AutoMapper) или сериализатор, которые работают с любыми типами данных.

Базовый пример: получаем значение свойства по имени

Допустим, у нас есть простой класс Person:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string City { get; set; }
}

Мы хотим написать метод, который принимает объект Person и строку с именем свойства (например, "Name" или "Age") и возвращает значение этого свойства. Вот как это делается с помощью рефлексии:

using System;
using System.Reflection;

public static object GetPropertyValue(object obj, string propertyName)
{
    if (obj == null) throw new ArgumentNullException(nameof(obj));
    if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("Имя свойства не может быть пустым", nameof(propertyName));

    // Получаем тип объекта
    Type type = obj.GetType();

    // Получаем информацию о свойстве по его имени
    PropertyInfo propertyInfo = type.GetProperty(propertyName);

    // Проверяем, существует ли такое свойство
    if (propertyInfo == null)
        throw new ArgumentException($"Свойство '{propertyName}' не найдено в типе '{type.Name}'");

    // Получаем значение свойства
    return propertyInfo.GetValue(obj);
}

Теперь используем наш метод:

var person = new Person { Name = "Анна", Age = 30, City = "Москва" };

string name = (string)GetPropertyValue(person, "Name");
int age = (int)GetPropertyValue(person, "Age");

Console.WriteLine($"Имя: {name}, Возраст: {age}"); // Вывод: Имя: Анна, Возраст: 30

Всё работает! Ключевые моменты:

  • obj.GetType() — получает объект Type, описывающий тип нашего объекта.
  • type.GetProperty("Name") — ищет публичное свойство с именем "Name". Возвращает PropertyInfo или null, если свойство не найдено.
  • propertyInfo.GetValue(obj) — возвращает значение этого свойства для конкретного экземпляра obj.

Важные нюансы и частые ошибки (то, о чём молчат джуны)

1. Регистрозависимость

Метод GetProperty по умолчанию чувствителен к регистру. Если вы укажете "name" вместо "Name", свойство не будет найдено. Чтобы сделать поиск нечувствительным к регистру, используйте перегрузку с флагами привязки:

PropertyInfo propertyInfo = type.GetProperty(propertyName, 
    BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

Теперь "name", "NAME" и "Name" будут работать одинаково.

2. Тип возвращаемого значения — object

GetValue возвращает object. Вам нужно явно приводить его к нужному типу, как мы сделали с (string) и (int). Если вы попытаетесь присвоить значение переменной другого типа без приведения, компилятор выдаст ошибку.

3. Вложенные свойства

Что если имя свойства выглядит как "Address.Street"? Простой GetProperty с этим не справится. Нужно разбить строку по точке и рекурсивно пройти по цепочке свойств. Вот пример:

public static object GetNestedPropertyValue(object obj, string propertyPath)
{
    string[] properties = propertyPath.Split('.');
    object currentObject = obj;

    foreach (string propName in properties)
    {
        if (currentObject == null) throw new NullReferenceException($"Объект на пути '{propName}' равен null");
        PropertyInfo prop = currentObject.GetType().GetProperty(propName);
        if (prop == null) throw new ArgumentException($"Свойство '{propName}' не найдено");
        currentObject = prop.GetValue(currentObject);
    }

    return currentObject;
}

4. Производительность

Рефлексия работает медленнее прямого доступа к свойствам. Если вы вызываете такой метод тысячи раз в секунду в цикле, это может стать узким местом. Для таких случаев рассмотрите кэширование PropertyInfo в словаре или использование делегатов (например, Func<T, object>), созданных через Delegate.CreateDelegate.

Продвинутый пример: универсальный метод с дженериками

Мы можем сделать метод ещё удобнее, используя дженерики, чтобы не писать приведение типов каждый раз:

public static T GetPropertyValue<T>(object obj, string propertyName)
{
    PropertyInfo propertyInfo = obj.GetType().GetProperty(propertyName);
    if (propertyInfo == null)
        throw new ArgumentException($"Свойство '{propertyName}' не найдено");

    return (T)propertyInfo.GetValue(obj);
}

// Использование:
string name = GetPropertyValue<string>(person, "Name");
int age = GetPropertyValue<int>(person, "Age");

Теперь код стал чище и безопаснее по типам.

Когда использовать рефлексию, а когда — нет?

Рефлексия — это как мощный, но опасный инструмент. Её стоит применять, когда:

  • Вы пишете библиотеки, фреймворки или инструменты, которые должны работать с любыми типами (сериализаторы, ORM, IoC-контейнеры).
  • Имя свойства приходит из внешних данных (конфиг, пользовательский ввод).
  • Нужно реализовать плагинную архитектуру.

Блоги

Рекомендаций по книгам