Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Чтобы проиллюстрировать процесс рефлексии специальных атрибутов, вставьте в решение новый проект консольного приложения по имени VehicleDescriptionAttributeReader. Добавьте в него ссылку на проект AttributedCarLibrary. Выполните приведенные далее команды CLI (каждая должна вводиться по отдельности):
dotnet new console -lang c# -n VehicleDescriptionAttributeReader -o .
VehicleDescriptionAttributeReader -f net5.0
dotnet sln .Chapter17_AllProjects.sln add .VehicleDescriptionAttributeReader
dotnet add VehicleDescriptionAttributeReader reference .AttributedCarLibrary
Поместите в файл Program.сs следующий код:
using System;
using AttributedCarLibrary;
Console.WriteLine("***** Value of VehicleDescriptionAttribute *****n");
ReflectOnAttributesUsingEarlyBinding();
Console.ReadLine();
static void ReflectOnAttributesUsingEarlyBinding()
{
// Получить объект Type, представляющий тип Winnebago.
Type t = typeof(Winnebago);
// Получить все атрибуты Winnebago.
object[] customAtts = t.GetCustomAttributes(false);
// Вывести описание.
foreach (VehicleDescriptionAttribute v in customAtts)
{
Console.WriteLine("-> {0}n", v.Description);
}
}
Метод Type.GetCustomAttributes() возвращает массив объектов со всеми атрибутами, примененными к члену, который представлен объектом Туре (булевский параметр управляет тем, должен ли поиск распространяться вверх по цепочке наследования). После получения списка атрибутов осуществляется проход по всем элементам VehicleDescriptionAttribute с отображением значения свойства Description.
Рефлексия атрибутов с использованием позднего связывания
В предыдущем примере для вывода описания транспортного средства типа Winnebago применялось ранее связывание. Это было возможно благодаря тому, что тип класса VehicleDescriptionAttribute определен в сборке AttributedCarLibrary как открытый член. Для рефлексии атрибутов также допускается использовать динамическую загрузку и позднее связывание.
Добавьте к решению новый проект консольного приложения по имени VehicleDescriptionAttributeReaderLateBinding, установите его в качестве стартового и скопируйте сборку AttributedCarLibrary.dll в каталог проекта (или в binDebugnet5.0, если вы работаете в Visual Studio). Модифицируйте файл Program.cs, как показано ниже:
using System;
using System.Reflection;
Console.WriteLine("***** Value of VehicleDescriptionAttribute *****n");
ReflectAttributesUsingLateBinding();
Console.ReadLine();
static void ReflectAttributesUsingLateBinding()
{
try
{
// Загрузить локальную копию сборки AttributedCarLibrary.
Assembly asm = Assembly.LoadFrom("AttributedCarLibrary");
// Получить информацию о типе VehicleDescriptionAttribute.
Type vehicleDesc =
asm.GetType("AttributedCarLibrary.VehicleDescriptionAttribute");
// Получить информацию о типе свойства Description.
PropertyInfo propDesc = vehicleDesc.GetProperty("Description");
// Получить все типы в сборке.
Type[] types = asm.GetTypes();
// Пройти по всем типам и получить любые атрибуты VehicleDescriptionAttribute.
foreach (Type t in types)
{
object[] objs = t.GetCustomAttributes(vehicleDesc, false);
// Пройти по каждому VehicleDescriptionAttribute и вывести
// описание, используя позднее связывание.
foreach (object o in objs)
{
Console.WriteLine("-> {0}: {1}n", t.Name,
propDesc.GetValue(o, null));
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Если вы прорабатывали примеры, рассмотренные ранее в главе, тогда приведенный код должен быть более или менее понятен. Единственный интересный момент здесь связан с применением метода PropertyInfo.GetValue(), который служит для активизации средства доступа к свойству. Вот как выглядит вывод, полученный в результате выполнения текущего примера:
***** Value of VehicleDescriptionAttribute *****
-> Motorcycle: My rocking Harley
-> HorseAndBuggy: The old gray mare, she ain't what she used to be...
-> Winnebago: A very long, slow, but feature-rich auto
Практическое использование рефлексии позднего связывания и специальных атрибутов
Хотя вы видели многочисленные примеры применения этих приемов, вас по-прежнему может интересовать, когда использовать рефлексию, динамическое связывание и специальные атрибуты в своих программах. Действительно, данные темы могут показаться в большей степени относящимися к академической стороне программирования (что в зависимости от вашей точки зрения может быть как отрицательным, так и положительным аспектом). Для содействия в отображении указанных тем на реальные ситуации необходим более серьезный пример. Предположим, что вы работаете в составе команды программистов, которая занимается построением приложения, соблюдая требование о том, что продукт должен быть расширяемым за счет использования добавочных сторонних инструментов.
Что понимается под расширяемостью? Возьмем IDE -среду Visual Studio. Когда это приложение разрабатывалось, в его кодовую базу были вставлены многочисленные "привязки", чтобы позволить другим производителям программного обеспечения подключать специальные модули к IDE - среде. Очевидно, что у разработчиков Visual Studio отсутствовал какой-либо способ установки ссылок на внешние сборки .NET Core, которые на тот момент еще не были созданы (и потому раннее связывание недоступно), тогда как они обеспечили наличие в приложении необходимых привязок? Ниже представлен один из возможных способов решения задачи.
1. Во-первых, расширяемое приложение должно предоставлять некоторый механизм ввода, позволяющий пользователю указать модуль для подключения (наподобие диалогового окна или флага командной строки). Это требует динамической загрузки.
2. Во-вторых, расширяемое приложение должно иметь возможность выяснять, поддерживает ли модуль корректную функциональность (такую как набор обязательных интерфейсов), необходимую для его подключения к среде. Это требует рефлексии.
3. В-третьих, расширяемое приложение должно получать ссылку на требуемую инфраструктуру (вроде набора интерфейсных типов) и вызывать члены для запуска лежащей в основе функциональности. Это может требовать позднего связывания.
Попросту говоря, если расширяемое приложение изначально запрограммировано для запрашивания специфических интерфейсов, то во время выполнения оно в состоянии выяснять, может ли быть активизирован интересующий тип. После успешного прохождения такой проверки тип может поддерживать дополнительные интерфейсы, которые формируют полиморфную фабрику его функциональности. Именно этот подход был принят командой разработчиков Visual Studio, и вопреки тому, что вы могли подумать, в нем нет ничего сложного!
Построение расширяемого приложения
В последующих разделах будет рассмотрен пример создания расширяемого приложения, которое может быть дополнено функциональностью внешних сборок. Расширяемое приложение образовано из следующих сборок.
• CommonSnappableTypes.dll. Эта сборка содержит определения типов, которые будут использоваться каждым объектом оснастки. На нее будет напрямую ссылаться расширяемое приложение.
• CSharpSnapIn.dll. Оснастка, написанная на С#, в которой задействованы типы из сборки CommonSnappableTypes.dll.