.NET 4.5 CustomReflectionContext: для чего это полезно?

Что нового в .NET Framework 4.5 Developer Preview упоминает

Возможность настраивать контекст отражения для переопределения значений по умолчанию отражения через класс CustomReflectionContext.

Какова цель ReflectionContext? MSDN не совсем понятна в этом вопросе.

Ответы

Ответ 1

В прошлом с .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 имела аналогичные требования для взаимодействия с отражением, необязательное-время выполнения - увеличение), это именно то, что должно быть встроено во время выполнения.

Новая модель выглядит так: базовый класс 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, который я выбираю для передачи, - он не знает, получает ли он объект виртуализованного или "реального" типа.)

Итак, это означает, что вам больше не нужны специальные hack-оболочки вокруг API отражения, если вы хотите получить информацию о виртуализованном типе.