Menu
Coddy logo textTech

Атрибуты и рефлексия

Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 44 из 70.

Атрибуты — это теги метаданных, которые вы прикрепляете к элементам кода, таким как классы, методы или свойства. Они не меняют напрямую то, как выполняется код, но предоставляют информацию, которую можно прочитать во время выполнения (runtime). Рефлексия — это механизм, который считывает эти метаданные.

Вы применяете атрибут, помещая его в квадратные скобки над целевым элементом:

[Obsolete("Use NewMethod instead")]
public void OldMethod() { }

[Serializable]
public class Person
{
    public string Name { get; set; }
}

Вы можете создавать пользовательские атрибуты, наследуясь от Attribute:

public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public AuthorAttribute(string name) => Name = name;
}

[Author("John")]
public class Calculator { }

Рефлексия позволяет проверять типы и читать атрибуты во время выполнения, используя пространство имен System.Reflection:

Type type = typeof(Calculator);
var attr = type.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine(attr.Name);  // John

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

MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
    Console.WriteLine(method.Name);

Атрибуты и рефлексия лежат в основе многих фреймворков: библиотеки сериализации используют их для управления выводом JSON, фреймворки для тестирования — для обнаружения тестовых методов, а контейнеры внедрения зависимостей — для автоматической связки сервисов.

challenge icon

Задание

Легко

Давайте создадим систему обнаружения плагинов, которая использует пользовательские атрибуты и рефлексию для автоматического поиска и описания классов, помеченных как плагины. Это похоже на то, как реальные фреймворки обнаруживают компоненты, методы тестирования или конечные точки API во время выполнения.

Вы организуете свой код в трех файлах:

  • PluginAttribute.cs: Создайте пользовательский атрибут с именем PluginAttribute в пространстве имен PluginSystem. Ваш атрибут должен:
    • Наследоваться от Attribute
    • Иметь два свойства только для чтения: Name (string) и Version (string)
    • Принимать оба значения через конструктор
  • Plugins.cs: Создайте несколько классов плагинов в пространстве имен PluginSystem. Некоторые должны быть декорированы вашим PluginAttribute, а один — нет:
    • ImageProcessor — помечен [Plugin("Image Processor", "1.0")]
    • DataExporter — помечен [Plugin("Data Exporter", "2.5")]
    • HelperUtility — НЕ помечен никаким атрибутом (это просто обычный класс)
    Каждый класс может быть пустым — нас интересует только обнаружение их метаданных с помощью рефлексии.
  • Program.cs: В основном файле используйте рефлексию, чтобы найти все классы, имеющие PluginAttribute. Для каждого обнаруженного плагина выведите информацию о нем. Вам потребуется:
    • Получить все типы из текущей сборки
    • Проверить каждый тип на наличие PluginAttribute
    • Извлечь и отобразить свойства атрибута Name и Version

Используйте System.Reflection для проверки типов и получения пользовательских атрибутов. Метод GetCustomAttribute<T>() возвращает null, если атрибут отсутствует, поэтому вы можете использовать это для фильтрации классов, которые являются настоящими плагинами.

Выведите каждый обнаруженный плагин в следующем формате, по одному на строку:

Plugin: {Name} (v{Version})

Плагины должны быть выведены в следующем порядке: сначала ImageProcessor, затем DataExporter. Класс HelperUtility не должен появляться в выводе, так как у него отсутствует атрибут.

Ожидаемый вывод:

Plugin: Image Processor (v1.0)
Plugin: Data Exporter (v2.5)

Эта задача демонстрирует, как фреймворки используют атрибуты в качестве маркеров и рефлексию для их обнаружения — эта же техника лежит в основе контейнеров внедрения зависимостей (dependency injection), библиотек сериализации и фреймворков для тестирования!

Шпаргалка

Атрибуты — это теги метаданных, помещаемые в квадратные скобки над элементами кода. Они предоставляют информацию, которую можно прочитать во время выполнения:

[Obsolete("Use NewMethod instead")]
public void OldMethod() { }

[Serializable]
public class Person
{
    public string Name { get; set; }
}

Создавайте пользовательские атрибуты путем наследования от Attribute:

public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public AuthorAttribute(string name) => Name = name;
}

[Author("John")]
public class Calculator { }

Рефлексия позволяет проверять типы и читать атрибуты во время выполнения, используя пространство имен System.Reflection:

Type type = typeof(Calculator);
var attr = type.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine(attr.Name);  // John

Динамически находите методы и свойства:

MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
    Console.WriteLine(method.Name);

Метод GetCustomAttribute<T>() возвращает null, если атрибут отсутствует, что позволяет фильтровать типы на основе наличия атрибута.

Попробуйте сами

using System;
using System.Reflection;
using PluginSystem;

class Program
{
    public static void Main(string[] args)
    {
        // TODO: Получить все типы из текущей сборки
        // Подсказка: Используйте Assembly.GetExecutingAssembly().GetTypes()
        
        // TODO: Пройти циклом по каждому типу и проверить, есть ли у него атрибут PluginAttribute
        // Подсказка: Используйте type.GetCustomAttribute<PluginAttribute>()
        
        // TODO: Если атрибут существует, вывести информацию о плагине в формате:
        // Plugin: {Name} (v{Version})
        
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование