Платформа .
NET предоставляет богатый API для доступа к метаданным во время выполнения программы.
Но механизм интроспекции предполагает позднюю ассоциацию с элементами программы путем указания их имен и подписей через соответствующие структуры данных.
Подобный код может привести к изменению логики программы на некорректную после рефакторинга (переименования, изменения порядка параметров) или обфускации метаданных.
Решением этой проблемы является использование синтаксического сахара, доступного в рамках технологии.
Деревья выражений и язык C#.
Чтобы не заходить слишком далеко, в качестве примера напишем простой класс.
Добавим к этому классу свойства, обеспечивающие доступ к его метаданным через механизм самоанализа:public class SimpleClass { private readonly string theValue; public SimpleClass(string value, int ratio) { theValue = value; } public string Value { get { return theValue; } } }
internal static PropertyInfo ValueProperty
{
get { return typeof(SimpleClass).
GetProperty("Value"); }
}
internal static FieldInfo ValueField
{
get { return typeof(SimpleClass).
GetField("theValue", BindingFlags.NonPublic | BindingFlags.Instance); }
}
internal static ConstructorInfo Constructor
{
get { return typeof(SimpleClass).
GetConstructor(new[] { typeof(string), typeof( }); }
}
Если вывести значения этих свойств в консоль, то мы получим строковые представления этих элементов программы.
Однако если вы переставите параметры конструктора с помощью механизма рефакторинга, встроенного в IDE (например, в Visual Studio это меню «Рефакторинг», пункт «Изменить порядок параметров»), то свойство, отражающее метаданные конструктора, будет возвращать пустую ссылку (т.е.
null).
.
А если еще и применить к сборке обфускацию, то отражение поля и свойства таким образом станет совершенно невозможным (кроме случаев использования атрибута ОбфускацияАтрибут ).
Чтобы выйти из ситуации, вооружимся деревьями выражений, лямбда-выражениями и обобщениями.
Из всего этого склеим вспомогательный метод:
static TMember Reflect<TDelegate, TMember>(Expression<TDelegate> memberAccess)
where TDelegate : class
where TMember : MemberInfo
{
if (memberAccess.Body is MemberExpression)
return ((MemberExpression)memberAccess.Body).
Member as TMember; else if (memberAccess.Body is NewExpression) return ((NewExpression)memberAccess.Body).
Constructor as TMember; else if (memberAccess.Body is MethodCallExpression) return ((MethodCallExpression)memberAccess.Body).
Method as TMember;
else return null;
}
Этот метод принимает два параметра типа: первый принимает делегат, используемый для разрешения подписи лямбда-выражения, которое будет передано в качестве аргумента, второй принимает тип отражаемого члена класса (например, FieldInfo для поля).
Лямбда-выражение, переданное в качестве аргумента, описывает доступ к интересующему нас члену класса.
Внутри метода осуществляется доступ к телу лямбда-выражения, представленному в виде дерева выражений.
Поскольку тело представляет собой доступ к члену класса, мы анализируем тип тела на предмет возможных вариантов:
- Доступ к собственности или полю;
- Вызов нового оператора (это выражение содержит ссылку на конструктор);
- Вызов метода.
Теперь вы готовы написать код отражения, который автоматически изменяется во время рефакторинга и защищен от алгоритмов обфускации.
internal static PropertyInfo ValueProperty
{
get { return Reflect<Func<SimpleClass, string>, PropertyInfo>(v => v.Value); }
}
internal static FieldInfo ValueField
{
get { return Reflect<Func<SimpleClass, string>, FieldInfo>(v => v.theValue); }
}
internal static ConstructorInfo Constructor
{
get { return Reflect<Func<string, int, SimpleClass>, ConstructorInfo>((a1, a2) => new SimpleClass(a1, a2)); }
}
В этом подходе не используются строковые литералы для представления имен членов класса и массивы типов для описания сигнатуры конструктора.
Почему этот код продолжает работать после запуска обфускатора? Ответ заключается в том, как компилятор C# генерирует код для деревьев выражений.
Если вы откроете сборку в ILDASM, вы увидите, что оператор LDTOKEN используется для загрузки метаданных члена класса, который работает с числовым токеном, указывающим расположение метаданных члена в соответствующей таблице в PE-файле.
Всегда есть НО
Этот метод подходит только для отражения элементов программы, доступных в текущей лексической области.Теги: #reflection #c-sharp #Chulan #.
net 4.0
-
Я Хочу Знать Все: Бизнес-Анализ. Часть 1
19 Oct, 24 -
Бесплатный Комплект Обучения Silverlight 4
19 Oct, 24 -
О Кадровых Изменениях В Top4Top
19 Oct, 24