В прошлом в .NET возникало напряжение между желанием иметь возможность автоматизировать определенные функции с помощью отражения и возможностью их настраивать. Например, возьмем панель «Свойства» в Visual Studio - в сценариях, где отображается какой-либо тип .NET (например, элемент управления на поверхности конструктора), он может автоматически обнаруживать и отображать каждое открытое свойство, определяемое этим типом.
Использование рефлексии для такого типа поведения на основе типов полезно, потому что это означает, что каждое свойство будет отображаться без необходимости что-либо делать разработчику элемента управления. Но это представляет проблему: что, если вы хотите настроить вещи, например, определение категоризации или пользовательский интерфейс редактирования для определенного свойства?
Классическим решением в .NET является добавление нескольких пользовательских атрибутов в соответствующие элементы. Однако одна из проблем заключается в том, что это может означать, что части вашего кода, которые выполняют значимую работу во время выполнения, в конечном итоге зависят от классов, которые делают что-либо только во время разработки - полагаясь на атрибуты, вы не можете отделить аспекты времени выполнения и времени разработки , Вы действительно хотите отправить код для пользовательского интерфейса дизайнера для панели свойств VS как часть библиотеки элементов управления, которая в конечном итоге окажется на компьютерах конечных пользователей?
Другая проблема заключается в том, что в некоторых ситуациях вы можете динамически решать, какие «свойства» вы предоставляете. Одним из старейших примеров этого (начиная с .NET 1.0) было добавление DataSet
в какой-то элемент управления сеткой (на стороне клиента или в Интернете). В строго типизированном наборе данных отражение может быть подходящим способом для сетки, чтобы определить, какие свойства предоставляет источник, но DataSet
также может использоваться динамически, поэтому вам нужен способ для сетки данных спросить во время выполнения, какие столбцы дисплей.
(Один из ответов на этот вопрос: правильно спроектировать свой пользовательский интерфейс! Создание таких сеток напрямую приводит к ужасному взаимодействию с пользователем. Однако многие люди хотят делать это ленивым образом, будь это хорошая идея или нет ... )
Таким образом, у вас возникает ситуация, когда иногда вам нужно поведение, основанное на отражении, но иногда вы хотите иметь возможность получить полный контроль во время выполнения.
Различные специальные решения появились для этого. У вас есть все семейство типов TypeDescriptor
и PropertyDescriptor
, которые обеспечивают своего рода виртуализируемое представление поверх отражения. По умолчанию это просто пропустило бы все прямо из отражения, но у типов есть возможность предоставить пользовательские дескрипторы во время выполнения, позволяя им изменять или даже полностью заменять то, как они выглядят. ICustomTypeDescriptor
является частью этого мира.
Это обеспечивает одно решение вопроса о том, что по умолчанию требуется поведение, основанное на отражении, с возможностью предоставления поведения, управляемого во время выполнения, если вы этого хотите. Но это не решает проблему, когда вы хотите делать это только во время разработки, и вам не нужно отправлять этот код как часть ваших распространяемых файлов времени выполнения.
Итак, несколько лет назад Visual Studio представила свои собственные специальные механизмы для увеличения информации о типах во время разработки. Существует ряд правил, основанных на соглашениях, в которых Visual Studio автоматически обнаруживает компоненты времени разработки, связанные с конкретными компонентами времени выполнения, что позволяет настраивать процесс проектирования без необходимости вставлять соответствующий код в свои распространяемые компоненты. Blend также использует этот механизм, хотя и с некоторыми изменениями, позволяющими предоставлять различные дизайнерские элементы для VS и Blend.
Конечно, ничего из этого не видно через обычные API отражения - VS и Blend имеют слой-обертку, который расположен поверх отражения, чтобы все это работало.
Итак, теперь у нас есть два уровня виртуализации, которые могут проходить через отражение или увеличивать то, что исходит из отражения ...
Похоже, что в .NET 4.5 команда CLR решила, что, поскольку различные группы уже занимались этим, а другие группы хотели сделать больше (команда MEF предъявляла аналогичные требования к опциональное поведение runtime-augmentation), это именно то, что нужно встроить в среду выполнения.
Новая модель выглядит так: базовый класс ReflectionContext
- это абстрактный API, с помощью которого вы можете получить виртуализированную версию API отражения. Это обманчиво просто, потому что одна из основных идей состоит в том, что вам больше не нужны специализированные API, такие как система дескрипторов типов, если ваша единственная цель - получить виртуализируемую оболочку поверх отражения - отражение теперь можно виртуализировать из коробки. Таким образом, вы можете написать такую вещь
public static void ShowAllAttributes(Type t)
{
foreach (Attribute attr in t.GetCustomAttributes(true))
{
Console.WriteLine(attr);
}
}
Теперь вы всегда могли написать это, но до .NET 4.5 код, подобный этому, всегда работал с информацией о «реальном» типе, потому что он использует Reflection. Но благодаря контекстам отражения теперь можно обеспечить это виртуализированным Type
. Итак, рассмотрим этот очень скучный тип:
class NoRealAttributes
{
}
Если вы просто передадите typeof(NoRealAttributes)
моему методу ShowAllAttributes
, он ничего не распечатает. Но я могу написать (несколько надуманный) собственный контекст отражения:
class MyReflectionContext : CustomReflectionContext
{
protected override IEnumerable<object> GetCustomAttributes(MemberInfo member, IEnumerable<object> declaredAttributes)
{
if (member == typeof(NoRealAttributes))
{
return new[] { new DefaultMemberAttribute("Foo") };
}
else
{
return base.GetCustomAttributes(member, declaredAttributes);
}
}
}
(Кстати, я думаю, что различие между CustomReflectionContext
и его основанием ReflectionContext
состоит в том, что последний определяет API для виртуализируемого контекста отражения, в то время как CustomReflectionContext
добавляет некоторых помощников, чтобы вам было легче реализовать такую вещь.) И теперь я могу использовать это, чтобы предоставить виртуализированную версию Type
для моего класса:
var ctx = new MyReflectionContext();
Type mapped = ctx.MapType(typeof(NoRealAttributes).GetTypeInfo());
ShowAllAttributes(mapped);
В этом коде mapped
по-прежнему ссылается на объект Type
, поэтому все, кто знает, как использовать API отражения, сможет работать с ним, но теперь будет сообщать о наличии атрибута, который не ' т на самом деле там. Конечно, Type
является абстрактным, поэтому у нас всегда есть кое-что из этого, и если вы позвоните mapped.GetType()
, вы увидите, что на самом деле это System.Reflection.Context.Custom.CustomType
, а не System.RuntimeType
, который вы обычно видите. И этот объект CustomType
принадлежит моему пользовательскому контексту, поэтому любые другие объекты API отражения, которые вы получаете через него (например, если вы написали mapped.Assembly.GetTypes()
), вы также получите настраиваемые объекты, которые проходят через мой пользовательский контекст, что есть возможность изменить что-либо еще, что выходит.
Таким образом, код может перемещаться по системе типов, используя настроенный объект Type
. Несмотря на то, что такой код использует обычный базовый API отражения, у меня теперь есть возможность настроить все, что из этого получится, если я сочту нужным.
Вы получаете это виртуализированное представление только по запросу. Например, MEF в .NET 4.5 ищет пользовательский атрибут, определяющий, что он должен использовать пользовательский пользовательский контекст отражения, но в противном случае будет возвращаться к обычному отражению. (А в случае моего ShowAllAttributes
метода он использует любой объект Type
, который я выбрал для передачи - он не знает, получает ли он объект виртуализированного или «реального» типа.)
Короче говоря, это означает, что вам больше не нужны специальные оболочки для API отражения, если вы хотите получить информацию о виртуализированных типах.