Атрибуты и рефлексия
Часть раздела Объектно-ориентированное программирование путешествия по 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, фреймворки для тестирования — для обнаружения тестовых методов, а контейнеры внедрения зависимостей — для автоматической связки сервисов.
Задание
ЛегкоДавайте создадим систему обнаружения плагинов, которая использует пользовательские атрибуты и рефлексию для автоматического поиска и описания классов, помеченных как плагины. Это похоже на то, как реальные фреймворки обнаруживают компоненты, методы тестирования или конечные точки 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})
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов