Как стать автором
Обновить

.NET Reflection. Упрощаем работу и используем в тестировании

Уровень сложностиСложный
Время на прочтение28 мин
Количество просмотров6.9K

В данной статье хочу немного рассказать о классе Reflector, который был написан для облегчения работы с объектами и методами предоставляемыми.NET Reflection API из пространства имен System.Reflection. Отражение позволяет выполнять задачи по исследованию сборок, типов, интерфейсов, методов с их параметрами, полей, свойств и событий путем получения информации, описывающей их структуру. Эта информация хранится в метаданных сборки и получить ее можно, используя предназначенные для этого объекты и методы API отражения. Она может потребоваться как просто для получения метаданных об интересуемых объектах, так и для генерации используемого их кода в момент работы приложения либо через сам Reflection API из пространства имен System.Reflection.Emit, либо через API LINQ Expressions из пространства имен System.Linq.Expressions. В данной статье мы не будем касаться генерации кода, а рассмотрим другую возможность, которую предоставляет отражение — это доступ к членам типов или к членам их экземпляров, вызов их методов и активацию событий.

Здесь надо отметить, что обращение к членам класса и вызов методов через механизм отражения имеет как недостатки, так и преимущества. Одним из преимуществ является возможность изучения внутренней структуры типа и получение доступа как к публичным, так и к закрытым его членам. Можно модифицировать закрытые поля, устанавливать свойства и вызывать методы. Но здесь также таится и недостаток в плане безопасности — без точного понимания процессов, происходящих при изменении внутреннего состояния объекта, можно привести его в неработоспособное состояние. Основным же недостатком механизма отражения является скорость его работы, поэтому его используют там, где скорость не является критическим фактором, например, тестирование или там, где используется позднее связывание и/или требуется информация о структуре типа (плагины, сериализаторы, интерфейсы с «утиной» типизацией и пр.).

Особенно хочу коснуться модульного тестирования, именно оно сподвигло меня доделать некогда сделанный в академических целях класс. Требовалось написать unit‑тесты на не один десяток методов, каждый из которых должен был запускаться с несколькими наборами параметров. Поэтому был использован подход, называемый DDT (Data Driven Testing) — управляемое данными тестирование и заключающийся в том, что на вход тестового метода подаются значения входных параметров и эталонный (ожидаемый) результат, с которым производится сравнение полученного (фактического) значения в результате выполнения тестируемого действия над входными параметрами.

Большинство методов в тестовых фреймворках оперируют значениями, приведенными к типу object. API отражения также оперирует значениями, приведенными к типу object, поэтому все преобразования и проверки типов ложатся на соответствующие фреймворки. Нам же фактически необходимо найти нужный метод, соответствующий входным параметрам. Например, сейчас можно в массив данных для тестирования записать тип или его экземпляр, наименование определенного в нем метода и некоторые его атрибуты, массив значений параметров и ожидаемый результат или массив результатов (поддерживаются также out параметры). Внутри тестового метода в самом простом случае нужно будет вызвать метод Reflector.CallMethod с переданными параметрами, получить фактический результат и сравнить его с ожидаемым. Т.е. никаких дескрипторов членов типа получать не нужно. Ниже приведен фрагмент тестов методов валидации строковых аргументов класса валидатора Argument из этой же библиотеки.

public static IEnumerable<object?[]> StringSuccessfulData
  => new object?[][]
  {
    new object?[] { nameof(Argument.NotNullOrEmpty), new object?[] { "X" } },
    new object?[] { nameof(Argument.NotNullOrEmpty), new object?[] { "ABC" } },
    new object?[] { nameof(Argument.NotNullOrWhitespace), new object?[] { "X" } },
    new object?[] { nameof(Argument.NotNullOrWhitespace), new object?[] { " Y " } },
    new object?[] { nameof(Argument.Empty), new object?[] { "" } },
    new object?[] { nameof(Argument.NotEmpty), new object?[] { " " } },
    new object?[] { nameof(Argument.NotEmpty), new object?[] { "A" } },
    new object?[] { nameof(Argument.Whitespace), new object?[] { "" } },
    new object?[] { nameof(Argument.Whitespace), new object?[] { "  " } },
    new object?[] { nameof(Argument.NotWhitespace), new object?[] { "  0" } },
    new object?[] { nameof(Argument.Contains), new object?[] { AlphaNumericSymbols, "456", TypedValue.DefaultOf<CultureInfo>() } },
    new object?[] { nameof(Argument.Contains), new object?[] { AlphaNumericSymbols, "FgHiJ", CultureInfo.CurrentCulture, CompareOptions.IgnoreCase } },
    new object?[] { nameof(Argument.NotContains), new object?[] { AlphaNumericSymbols, "465", TypedValue.DefaultOf<CultureInfo>() } },
    new object?[] { nameof(Argument.NotContains), new object?[] { AlphaNumericSymbols, "FgHiJ", CultureInfo.CurrentCulture, CompareOptions.None } },
    new object?[] { nameof(Argument.StartsWith), new object?[] { AlphaNumericSymbols, " 0123456789ABC", TypedValue.DefaultOf<CultureInfo>() } },
    new object?[] { nameof(Argument.StartsWith), new object?[] { AlphaNumericSymbols, " 0123456789abc", CultureInfo.CurrentCulture, CompareOptions.IgnoreCase } },
    new object?[] { nameof(Argument.NotStartsWith), new object?[] { AlphaNumericSymbols, "0123456789", TypedValue.DefaultOf<CultureInfo>() } },
    new object?[] { nameof(Argument.NotStartsWith), new object?[] { AlphaNumericSymbols, " 0123456789abc", CultureInfo.CurrentCulture, CompareOptions.None } },
    new object?[] { nameof(Argument.EndsWith), new object?[] { AlphaNumericSymbols, "MNOPQRSTUVWXYZ", TypedValue.DefaultOf<CultureInfo>() } },
    new object?[] { nameof(Argument.EndsWith), new object?[] { AlphaNumericSymbols, "PQRStUVwXyZ", CultureInfo.CurrentCulture, CompareOptions.IgnoreCase } },
    new object?[] { nameof(Argument.NotEndsWith), new object?[] { AlphaNumericSymbols, "STUVXYZ", TypedValue.DefaultOf<CultureInfo>() } },
    new object?[] { nameof(Argument.NotEndsWith), new object?[] { AlphaNumericSymbols, "PQRStUVwXyZ", CultureInfo.CurrentCulture, CompareOptions.None } },
    new object?[] { nameof(Argument.Match), new object?[] { RegexSymbols, @"^\(\w+[:](?:\s+\w+)+\)$", RegexOptions.None } },
    new object?[] { nameof(Argument.NotMatch), new object?[] { RegexSymbols, @"^\(\w+[-](?:\s+\w+)+\)$", RegexOptions.None } },
  };

[Theory]
[MemberData(nameof(StringSuccessfulData))]
public void StringValidationSuccessful(object name, object parameters)
{
  var methodName = Argument.That.InstanceOf<string>(name);
  var positionalParams = Argument.That.InstanceOf<IList<object?>>(parameters);
  var result = Reflector.CallMethod(Argument.That, methodName, MemberAccessibility.Public, null, positionalParams, null, null);
  Assert.Equal(positionalParams[0], result);
}

Простой сценарий доступа к членам типов через стандартный API заключается в получении метаинформации о требуемом члене типа или его экземпляра, для чего надо вызвать метод, возвращающий необходимые метаданные. Обычно в качестве параметров мы должны знать тип, в котором определен интересуемый нас член типа, его наименование, тип доступа (публичный или непубличный), область определения (статический или экземплярный) и тип его значения (для полей и простых свойств) или сигнатуру (для методов, свойств-индексаторов и конструкторов). Эти параметры запроса передаются функциям API отражения, которые возвращают найденный единственный член типа или коллекцию найденных согласно указанному условию членов типа. Возвращаемые члены типов могут быть определены как в самом исследуемом типе, так и в его базовых классах, что опять же зависит от задаваемых условий.

Сделаем небольшое отступление и договоримся о последующей терминологии. Будем называть универсальными, типы с типами передаваемыми в качестве параметров, например, GenType<TA, TB>, где TA и TB будут аргументами универсального типа GenType. По такому же принципу будем называть методы универсальными, например, GenMethod<TX, TY>(TX x, TY y). Универсальные типы и методы с неозначенными аргументами типами называются определениями универсальных типов и методов соответственно. Универсальные типы и методы называются открытыми, если не все аргументы типы означены. В противном случае они называются закрытыми.

Рассмотрим простой пример установки нового значения закрытого статического поля класса с возвратом его предыдущего значения. В отличие от обычного кода здесь мы первой строкой получаем дескриптор этого статического поля, объявленного в классе A, а затем вызываем соответствующие методы получения и установки значения этого поля. Во втором методе показано, что функционал класса Reflector может выполнить все эти действия за вас. Надо отметить, что здесь для упрощения опущены проверки на null-значение параметра, которое не допускается, т.к. невозможно извлечь тип объекта. Для этого в вышеупомянутом классе пустое значение конкретного типа можно передать через экземпляр класса TypedValue, либо использовать метод с универсальным параметром типа значения. В случае же передачи методу значения с несовпадающим типом поля int будет сгенерировано исключение об отсутствии указанного поля в типе.

public class A
{
  private static int value = 100;
}

internal static class Example_1
{
  internal static object ReplaceValue(object newValue)
  {
    var fieldInfo = typeof(A).GetField("value", BindingFlags.Static | BindingFlags.NonPublic);
    var oldValue = fieldInfo.GetValue(null);
    fieldInfo.SetValue(null, newValue);
    return oldValue;
  }

  internal static object ReplaceValue2(object newValue)
  {
    return Reflector.ReplaceFieldValue<A>("value", MemberAccessibility.Private, newValue);
  }
}

Рассмотрим уже другой пример с универсальным типом, где типом поля является аргумент класса, в котором оно объявлено. Для каждого закрытого универсального типа с уникальным набором его аргументов будет создан свой экземпляр статического члена типа. Пусть в метод, устанавливающий значение этого поля, передается приведенное к типу object значение. Поэтому мы должны определить тип данного значения, получить определение универсального типа, из которого применив в качестве параметра тип переданного значения поля сконструировать закрытый универсальный тип. Здесь также приведен второй метод, выполняющий то же действие, но с использованием класса Reflector.

public class A<T>
{
  private static T value;
}

internal static class Example_2
{
  internal static object ReplaceValue(object newValue)
  {
    var argType = newValue.GetType();
    var classType = typeof(A<>).MakeGenericType(argType);
    var fieldInfo = classType.GetField("value", BindingFlags.Static | BindingFlags.NonPublic);
    var oldValue = fieldInfo.GetValue(null);
    fieldInfo.SetValue(null, newValue);
    return oldValue;
  }

  internal static object ReplaceValue2(object newValue)
  {
    return Reflector.ReplaceFieldValue(typeof(A<>), "value", MemberAccessibility.Private, null, newValue);
  }
}

Далее рассмотрим вызов универсального метода универсального класса. Этот код уже более сложен, хотя и имеет всего два обобщающих аргумента – один для типа, другой для метода. В листинге ниже приведены два метода вызывающие один и тот же метод Replace класса B<T>. В первом методе приведен код, который надо написать в обычном порядке для выполнения вышеописанного действия. Второй метод использует функционал класса Reflector.

public class B<T>
{
  internal static T Replace<L>(L list, int index, T newValue)
    where L : IList<T>
  {
    var oldValue = list[index];
    list[index] = newValue;
    return oldValue;
  }  
}

internal static class Example_3
{
  internal static object ReplaceListItem(object list, int index, object newItem)
  {
    var argType = newItem.GetType();
    var listType = list.GetType();
    var listIFace = typeof(IList<>).MakeGenericType(argType);
    var classType = typeof(B<>).MakeGenericType(argType);
    var methodDef = classType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
      .Single(methodInfo =>
      {
        if (methodInfo.Name != "Replace")
          return false;
        var arguments = methodInfo.GetGenericArguments();
        var parameters = methodInfo.GetParameters();
        return arguments.Length == 1 && parameters.Length == 3
          && parameters[0].ParameterType == arguments[0]
          && parameters[1].ParameterType == typeof(int)
          && parameters[2].ParameterType == argType;
      });
    var methodRes = methodDef.MakeGenericMethod(listType);
    return methodRes.Invoke(null, new object[] { list, index, newItem });
  }

  internal static object ReplaceListItem2(object list, int index, object newItem)
  {
    return Reflector.CallMethod(typeof(B<>), "Replace", MemberAccessibility.Assembly, null, null, new[] { list, index, newItem }, null, null);
  }
}

А если таких методов будет множество, и они будут иметь большее число параметров?! Поэтому и возникла мысль упростить такие вызовы и автоматизировать выбор подходящих членов типа на основе значений входных параметров и ожидаемых типов возвращаемых значений.

Класс Reflector позволяет осуществлять получение дескрипторов полей, свойств, методов, конструкторов и событий с означенными аргументами универсальных типов и методов на основе типов параметров и ожидаемых типов возвращаемых значений. Также он позволяет осуществлять доступ к значениям таких членов типа как поля и свойства на чтение и запись, осуществлять вызов как синхронных, так и асинхронных методов, конструкторов и событий.

Во всех методах работы с объектами отражения класса Reflector в качестве первого параметра может быть указан либо экземпляр объекта требуемого типа для работы с его экземплярными членами, либо сам тип для работы с его статическими членами. Также тип может быть указан в обобщающих параметрах методов и опущен в обычных параметрах. Другим параметром является наименование члена, которое не указывается только для членов конструкторов. Еще одним общим параметром является значение флагов перечислимого типа MemberAccessibility:
None – отсутствие флагов;
IgnoreCase – флаг игнорирования регистра при сравнении наименований членов типа и параметров метода;
DeclaredOnly – флаг, допускающий только экземплярные члены, определенные в указанном типе. В отсутствии данного флага допускаются экземплярные члены всей иерархии наследования;
FlattenHierarchy – флаг, допускающий статические члены всей иерархии типов. В отсутствии данного флага допускаются статические члены, определенные только в указанном типе;
TopHierarchy – указывает на то, что в случае обнаружения нескольких подходящих под условие членов типа на разных уровнях наследования, будет взят тот, что находится выше других в списке наследования. В случае отсутствия данного флага и обнаружения нескольких подходящих методов на разных уровнях наследования или на одном уровне наследования независимо от наличия данного флага, будет сгенерировано исключение о неоднозначности выбора члена типа.
Family – указывает что будут допускаться члены типа с уровнем доступа Family (protected в C#);
Assembly – указывает что будут допускаться члены типа с уровнем доступа Assembly (internal в C#);
FamilyOrAssembly – указывает что будут допускаться члены типа с уровнем доступа Family или Assembly (internal protected в C#);
FamilyAndAssembly – указывает что будут допускаться члены типа с уровнем доступа Family и Assembly (private protected в C#);
Private – указывает что будут допускаться члены типа с уровнем доступа Private (private в C#);
Public – указывает что будут допускаться члены типа с уровнем доступа Public (public в C#);
NonPublic – является комбинацией всех флагов с непубличным уровнем доступа;
AnyAccess – является комбинацией всех флагов уровня доступа;
MemberAccessMask – является маской для всех флагов уровня доступа.

Далее опционально могут следовать два массива аргументов универсального типа и метода. Первый массив аргументов универсального типа передается в параметре typeArguments, второй массив аргументов универсального метода передается в параметре methodArguments. Применяются следующие правила интерпретации этих параметров.
Если указан закрытый универсальный тип для обнаружения искомого члена, то данный параметр указывать не требуется. Если же данный параметр будет указан, то будет осуществлена проверка на равенство размерности количества аргументов универсального типа и указанного массива. Далее будут сравниваться каждый аргумент универсального типа с элементом массива в соответствующей позиции, но только в том случае если элемент массива указан, т.е. не содержит нулевого значения. Таким образом длинной массива аргументов мы можем вводить ограничение арности аргументов универсального типа или метода, хотя сами аргументы можем не указывать.

Также мы можем указать определение универсального типа для обнаружения искомого члена. При указании массива аргументов в данном случае функционал отражения класса Reflector будет пытаться означить определение универсального типа указанными аргументами. В случае если какой-то аргумент будет не задан или сам массив аргументов, то функционал попытается вывести его, основываясь на использовании, например, из типа значения поля, свойства или параметров метода. В случае успешного означивания аргументов определения универсального типа будет создан закрытый универсальный тип, в котором уже будет производится обнаружение искомого члена.

Вышеописанные правила также могут быть применены и к аргументам универсальных методов. Если массив типов аргументов будет указан, то параметры с типами этих аргументам буду проверяться на соответствие, если же аргументы будут не указаны или указаны частично, то они будут означиваться по использованию. Подходящие универсальные методы должны иметь количество аргументов, равное длине массива передаваемых аргументов в случае его задания, хотя сами элементы массива могут быть не заданы, возлагая ответственность за их определение на функционал класса Reflector.

Здесь надо отметить, что передача пустого массива аргументов явно говорит о том, что тип или метод не должны быть универсальными.

Для параметризированных членов, а это, свойства с индексаторами, методы и конструкторы указываются типы параметров (для функций получения дескрипторов членов) или значения параметров (для функций доступа к членам). Параметры могут передаваться как позиционные в списке, так и поименованные в словаре (допускается указание обеих).

Позиционные параметры начинаются с нулевого индекса и каждое значение будет соответствовать параметру члена в соответствующей позиции. Необязательные параметры имеющие значения по умолчанию могут быть не указаны, в этом случае они инициализируются значением по умолчанию, указанным в параметре. Сопоставление поименованных параметров происходит по их именам в соответствии с флагом IgnoreCase из вышеописанного перечислимого типа MemberAccessibility, т.е. с учетом чувствительности к регистру или без него. После вызова методов, имеющих параметры со значением, передающимся по ссылке или возвращающимся по ней, в соответствующих этим параметрам позициях списка или словаря будут содержаться измененные или возвращенные вызванным методом значения. При сопоставлении типов передаваемых параметров применяется правило вариантности, т.е. тип значения, передаваемый в метод, должен быть равен или быть наследован от типа параметра, возвращаемый тип должен быть равен типу параметра или находится в его иерархии наследования. В случае параметра, передаваемого по ссылке, тип его значения должен быть инвариантен, т.е. равен типу значения параметра метода. В случае если в параметре передается значение null, то его тип мы получить не можем и этот параметр будет игнорирован при сопоставлении. Поэтому существует класс-заглушка TypedValue который можно указывать в качестве передаваемого значения. Используя его, можно передавать значения любых типов, даже интерфейсов. Default-значения типов желательно передавать так:

TypedValue.DefaultOf<T>()
TypedValue.DefaultOf(Type type)

Строго типизированное значение можно передать в методе с универсальным параметром типа значения или так:

TypedValue.ValueOf<T>(T? value)
TypedValue.ValueOf(object? value, Type type)

Для методов возвращающих значения полей, свойств или методов, можно указать ковариантный тип типу его значения или не указывать. Если значение типа не указано, то выбираются члены с любым типом возвращаемого значения и другими удовлетворяющими условиям параметрами.

Методы класса Reflector могут возвращать три ожидаемых вида ошибки в исключении InvalidOperationException: "член не обнаружен в типе", "неоднозначное обнаружение члена в типе" и "не все аргументы означены" для универсального типа или метода.

Для работы с каждым видом членов типа существует две категории методов: прямые методы, которые генерируют исключение при отсутствии искомого члена и пробные методы с префиксом Try которые возвращают в этой ситуации значение false. Количество вариантов методов просто огромно, поэтому здесь мы будем приводить по одному виду из каждой категории. Интересующиеся могут посмотреть весь список методов класса Reflector на Wiki или через браузер объектов в Visual Studio.

Давайте перейдем к рассмотрению самого API.

  1. Методы для работы с полями.

Существует несколько видов методов для работы с полями: получение дескриптора поля и получение, установка, замена и обмен значения поля.

// Получение дескриптора экземплярного поля объекта
public static FieldInfo GetField(object source, string name, MemberAccessibility memberAccessibility, Type? valueType);
// Получение дескриптора статического поля типа
public static FieldInfo GetField(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, Type? valueType);

// Получение значения экземплярного поля объекта
public static object? GetFieldValue(object source, string name, MemberAccessibility memberAccessibility, Type? valueType);
// Получение значения статического поля типа
public static object? GetFieldValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, Type? valueType);

// Установка значения экземплярного поля объекта
public static void SetFieldValue(object source, string name, MemberAccessibility memberAccessibility, object? value);
// Установка значения статического поля типа
public static void SetFieldValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, object? value);

// Замена значения экземплярного поля объекта
public static object? ReplaceFieldValue(object source, string name, MemberAccessibility memberAccessibility, object? value);
// Замена значения статического поля типа
public static object? ReplaceFieldValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, object? value);

// Обмен значения экземплярного поля объекта
public static void ExchangeFieldValue(object source, string name, MemberAccessibility memberAccessibility, ref object? value);
// Обмен значения статического поля типа
public static void ExchangeFieldValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, ref object? value);
  1. Методы для работы со свойствами.

Данные методы по параметрам идентичны методам работы с полями.

// Получение дескриптора экземплярного свойства объекта
public static PropertyInfo GetProperty(object source, string name, MemberAccessibility memberAccessibility, Type? valueType);
// Получение дескриптора статического свойства типа
public static PropertyInfo GetProperty(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, Type? valueType);

// Получение значения экземплярного свойства объекта
public static object? GetPropertyValue(object source, string name, MemberAccessibility memberAccessibility, Type? valueType);
// Получение значения статического свойства типа
public static object? GetPropertyValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, Type? valueType);

// Установка значения экземплярного свойства объекта
public static void SetPropertyValue(object source, string name, MemberAccessibility memberAccessibility, object? value);
// Установка значения статического свойства типа
public static void SetPropertyValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, object? value);

// Замена значения экземплярного свойства объекта
public static object? ReplacePropertyValue(object source, string name, MemberAccessibility memberAccessibility, object? value);
// Замена значения статического свойства типа
public static object? ReplacePropertyValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, object? value);

// Обмен значения экземплярного свойства объекта
public static void ExchangePropertyValue(object source, string name, MemberAccessibility memberAccessibility, ref object? value);
// Обмен значения статического свойства типа
public static void ExchangePropertyValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, ref object? value);
  1. Методы для работы с индексаторами (индексируемыми свойствами).

В данных методах по сравнению с обычными свойствами добавлены параметры значений индексаторов. Параметры могут задаваться как позиционные, так и поименованные.

// Получение дескриптора экземплярного индексатора объекта
public static PropertyInfo GetProperty(object source, string name, MemberAccessibility memberAccessibility, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, Type? valueType);
// Получение дескриптора статического индексатора типа
public static PropertyInfo GetProperty(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, Type? valueType);

// Получение значения экземплярного индексатора объекта
public static object? GetPropertyValue(object source, string name, MemberAccessibility memberAccessibility, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, Type? valueType);
// Получение значения статического индексатора типа
public static object? GetPropertyValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, Type? valueType);

// Установка значения экземплярного индексатора объекта
public static void SetPropertyValue(object source, string name, MemberAccessibility memberAccessibility, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, object? value);
// Установка значения статического индексатора типа
public static void SetPropertyValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, object? value);

// Замена значения экземплярного индексатора объекта
public static object? ReplacePropertyValue(object source, string name, MemberAccessibility memberAccessibility, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, object? value);
// Замена значения статического индексатора типа
public static object? ReplacePropertyValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, object? value);

// Обмен значения экземплярного индексатора объекта
public static void ExchangePropertyValue(object source, string name, MemberAccessibility memberAccessibility, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, ref object? value);
// Обмен значения статического индексатора типа
public static void ExchangePropertyValue(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues, ref object? value);
  1. Методы для работы с методами.

Данные методы позволяют вызывать экземплярные или статические методы типов. Поддерживаются как синхронные, так и асинхронные методы. Асинхронные методы поддерживают вызов методов возвращающих любые awaitable типы. Для асинхронных типов в качестве фильтра должен указываться тип значения возвращаемый awaiter-ом, т.е. не Task<TResult>, а именно тип TResult.Для всех методов где отсутствует параметр типа возвращаемого значения подразумевается что вызываемый метод возвращает тип void.

// Получение дескриптора экземплярного метода объекта
public static MethodInfo GetMethod(object source, string name, MemberAccessibility memberAccessibility, IList<Type?>? methodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes, Type? returnType);
// Получение дескриптора статического метода типа
public static MethodInfo GetMethod(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IList<Type?>? methodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes, Type? returnType);

// Вызов экземплярного метода объекта
public static object? CallMethod(object source, string name, MemberAccessibility memberAccessibility, IList<Type?>? methodArguments, IList<object?>? positionalParameterValues, IDictionary<string, object?>? namedParameterValues, Type? returnType);
// Вызов статического метода типа
public static object? CallMethod(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IList<Type?>? methodArguments, IList<object?>? positionalParameterValues, IDictionary<string, object?>? namedParameterValues, Type? returnType);

// Вызов экземплярного асинхронного метода объекта
public static Task<object?> CallMethodAsync(object source, string name, MemberAccessibility memberAccessibility, IList<Type?>? methodArguments, IList<object?>? positionalParameterValues, IDictionary<string, object?>? namedParameterValues, Type? returnType);
// Вызов статического асинхронного метода типа
public static Task<object?> CallMethodAsync(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IList<Type?>? methodArguments, IList<object?>? positionalParameterValues, IDictionary<string, object?>? namedParameterValues, Type? returnType);
  1. Методы для работы с конструкторами.

Данные методы вызывают конструкторы указанного типа и возвращают его сконструированный экземпляр.

// Получение дескриптора конструктора
public static ConstructorInfo GetConstructor(Type sourceType, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues);
// Создание экземпляра типа с использованием конструктора удовлетворяющего параметрам
public static object Construct(Type sourceType, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IReadOnlyList<object?>? positionalParameterValues, IReadOnlyDictionary<string, object?>? namedParameterValues);
  1. Методы для работы с событиями.

Данные методы позволяют получать информацию о дескрипторе события и выполнять с ним следующие действия: добавлять обработчик, удалять обработчик, делать очистку всех обработчиков и вызывать обработчики события как синхронно, так и асинхронно. Несмотря на то, что возвращаемые значения делать в обработчиках событий не рекомендуется, т.к. это лишено смысла, тем не менее, здесь сделана поддержка работы с такими обработчиками. Также сделана поддержка асинхронных awaitable-обработчиков, реализовать работу с которыми в обычном коде разработчик должен сам, тем более сделать это не так и сложно. Надо упомянуть, что сделана еще и поддержка асинхронного вызова стандартных синхронных обработчиков - каждый обработчик вызывается в отдельной задаче. Методы осуществляющие такие вызовы содержат параметр TaskFactory, с помощью которого создается задача для каждого обработчика. Ниже будут приведены методы для работы со стандартными обработчиками. Методы очистки и вызова обработчиков события содержат дополнительный параметр-делегат eventHandlerResolver. Он требуется в том случае, если хранение обработчиков события реализовано нестандартно, т.е. делегат не хранится в защищенном поле с именем идентичным наименованию события. В этом случае методу нужно иметь доступ к делегатам для их очистки или вызова, что и должен предоставлять вышеупомянутый резолвер. Либо вызывать событие стандартно, через метод, предоставляемый реализацией события.

// Получение дескриптора экземплярного события объекта
public static EventInfo GetEvent(object source, string name, MemberAccessibility memberAccessibility, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes);
// Получение дескриптора статического события типа
public static EventInfo GetEvent(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes)

// Добавление экземплярного метода-обработчика в экземплярное событие объекта
public static void AddEventHandler(object eventSource, string eventName, MemberAccessibility eventAccessibility, object methodSource, string methodName, MemberAccessibility methodAccessibility, IList<Type?>? methodMethodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes);
// Добавление статического метода-обработчика в экземплярное событие объекта
public static void AddEventHandler(object eventSource, string eventName, MemberAccessibility eventAccessibility, Type methodType, string methodName, MemberAccessibility methodAccessibility, IList<Type?>? methodTypeArguments, IList<Type?>? methodMethodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes);
// Добавление экземплярного метода-обработчика в статическое событие типа
public static void AddEventHandler(Type eventType, string eventName, MemberAccessibility eventAccessibility, IList<Type?>? eventTypeArguments, object methodSource, string methodName, MemberAccessibility methodAccessibility, IList<Type?>? methodMethodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes);
// Добавление статического метода-обработчика в статическое событие типа
public static void AddEventHandler(Type eventType, string eventName, MemberAccessibility eventAccessibility, IList<Type?>? eventTypeArguments, Type methodType, string methodName, MemberAccessibility methodAccessibility, IList<Type?>? methodTypeArguments, IList<Type?>? methodMethodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes);

// Удаление экземплярного метода-обработчика из экземплярного события объекта
public static void RemoveEventHandler(object eventSource, string eventName, MemberAccessibility eventAccessibility, object methodSource, string methodName, MemberAccessibility methodAccessibility, IList<Type?>? methodMethodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes);
// Удаление статического метода-обработчика из экземплярного события объекта
public static void RemoveEventHandler(object eventSource, string eventName, MemberAccessibility eventAccessibility, Type methodType, string methodName, MemberAccessibility methodAccessibility, IList<Type?>? methodTypeArguments, IList<Type?>? methodMethodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes);
// Удаление экземплярного метода-обработчика из статического события типа
public static void RemoveEventHandler(Type eventType, string eventName, MemberAccessibility eventAccessibility, IList<Type?>? eventTypeArguments, object methodSource, string methodName, MemberAccessibility methodAccessibility, IList<Type?>? methodMethodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes);
// Удаление статического метода-обработчика из статического события типа
public static void RemoveEventHandler(Type eventType, string eventName, MemberAccessibility eventAccessibility, IList<Type?>? eventTypeArguments, Type methodType, string methodName, MemberAccessibility methodAccessibility, IList<Type?>? methodTypeArguments, IList<Type?>? methodMethodArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes);

// Очистка всех методов-обработчиков экземплярного события объекта
public static void ClearEventHandlers(object source, string name, MemberAccessibility memberAccessibility, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes, Func<EventInfo, object, Delegate?>? eventHandlerResolver = null);
// Очистка всех методов-обработчиков статического события типа
public static void ClearEventHandlers(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IReadOnlyList<Type?>? positionalParameterTypes, IReadOnlyDictionary<string, Type?>? namedParameterTypes, Func<EventInfo, Delegate?>? eventHandlerResolver = null);

// Вызов методов-обработчиков экземплярного события объекта
public static void RaiseEvent(object source, string name, MemberAccessibility memberAccessibility, IList<object?>? positionalParameterValues, IDictionary<string, object?>? namedParameterValues, Func<EventInfo, object, Delegate?>? eventHandlerResolver = null);
// Вызов методов-обработчиков статического события типа
public static void RaiseEvent(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IList<object?>? positionalParameterValues, IDictionary<string, object?>? namedParameterValues, Func<EventInfo, Delegate?>? eventHandlerResolver = null);

// Асинхронный вызов синхронных методов-обработчиков экземплярного события объекта
public static Task RaiseEventAsync(object source, string name, MemberAccessibility memberAccessibility, IList<object?>? positionalParameterValues, IDictionary<string, object?>? namedParameterValues, TaskFactory? taskFactory, Func<EventInfo, object, Delegate?>? eventHandlerResolver = null);
// Асинхронный вызов синхронных методов-обработчиков статического события типа
public static Task RaiseEventAsync(Type sourceType, string name, MemberAccessibility memberAccessibility, IList<Type?>? typeArguments, IList<object?>? positionalParameterValues, IDictionary<string, object?>? namedParameterValues, TaskFactory? taskFactory, Func<EventInfo, Delegate?>? eventHandlerResolver = null);

Далее, для наглядности, приведу небольшой пример работы с экземпляром класса ObservableCollection<string> с использованием методов класса Reflector.

internal static class Example
{
  private static void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    => Console.WriteLine($"event: CollectionChanged, action: '{e.Action}'");

  private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
    => Console.WriteLine($"event: PropertyChanged, property: '{e.PropertyName}'");

  public static void Test()
  {
    //  Создание экземпляра класса ObservableCollection
    var listContent = new[] { "One", "Two", "Three", "Four", "Five" };
    var typeArguments = new Type[] { null };
    
    var observableList = Reflector.Construct(typeof(ObservableCollection<>), MemberAccessibility.Public, typeArguments, new[] { listContent }, null);
    //var observableList = Reflector.Construct(typeof(ObservableCollection<string>), MemberAccessibility.Public, null, new[] { listContent }, null);
    //var observableList = Reflector.Construct<ObservableCollection<string>>(MemberAccessibility.Public, new[] { listContent }, null);
    
    //  Получение свойства счетчика элементов списка
    var count = Reflector.GetPropertyValue(observableList, "Count", MemberAccessibility.Public, null);
    
    Console.WriteLine("List count: {0}", count);
    
    //  Добавление обработичика события PropertyChanged
    Reflector.AddEventHandler(
      observableList, "CollectionChanged", MemberAccessibility.Public,
      typeof(Sample), "OnCollectionChanged", MemberAccessibility.Private, null, null,
      new[] { typeof(object), typeof(NotifyCollectionChangedEventArgs) }, null);
    
    //  Добавление обработичика события PropertyChanged
    Reflector.AddEventHandler(
      observableList, "PropertyChanged", MemberAccessibility.Family,
      typeof(Sample), "OnPropertyChanged", MemberAccessibility.Private, null, null,
      new[] { typeof(object), typeof(PropertyChangedEventArgs) }, null);

    //  Добавление элемента в список
    var addItem = "Last";
    Console.WriteLine();
    Console.WriteLine("Add item: '{0}'", addItem);
    
    Reflector.CallMethod(observableList, "Add", MemberAccessibility.Public, null, new object[] { addItem }, null);
    
    Console.WriteLine("List content: '{0}'", string.Join(',', (IEnumerable<string>)observableList));
    
    //  Замена элемента списка по индексу
    var updateItem = "ThreeNew";
    var updateIndex = 2;
    Console.WriteLine();
    Console.WriteLine("Replace item: '{0}' at position: '{1}'", updateItem, updateIndex);
    
    var oldValue = Reflector.ReplacePropertyValue(observableList, "Item", MemberAccessibility.Public, new object[] { updateIndex }, null, updateItem);
    
    Console.WriteLine("Previous item: '{0}'", oldValue);
    Console.WriteLine("List content: '{0}'", string.Join(',', (IEnumerable<string>)observableList));
    
    //  Вызов метода Move
    var moveFrom = 0;
    var moveTo = 3;
    Console.WriteLine();
    Console.WriteLine("Move item from position: '{0}' to position: '{1}'", moveFrom, moveTo);

    Reflector.CallMethod(observableList, "Move", MemberAccessibility.Public, null, new object[] { moveFrom, moveTo }, null);
    
    Console.WriteLine("List content: '{0}'", string.Join(',', (IEnumerable<string>)observableList));

    //  Вызов события PropertyChanged из стороннего кода
    var propertyName = "MyProperty";
    Console.WriteLine();
    Console.WriteLine("Raise custom PropertyChanged event: '{0}'", propertyName);

    Reflector.RaiseEvent(observableList, "PropertyChanged", MemberAccessibility.Family,
      new object[] { null, new PropertyChangedEventArgs(propertyName) }, null);

    //  Удаление конкретного обработчика из события
    Reflector.RemoveEventHandler(
      observableList, "PropertyChanged", MemberAccessibility.Family,
      typeof(Sample), "OnPropertyChanged", MemberAccessibility.Private, null, null,
      new[] { typeof(object), typeof(PropertyChangedEventArgs) }, null);

    //  Удаление всех обработчиков из события
    Reflector.ClearEventHandlers(
      observableList, "CollectionChanged", MemberAccessibility.Public,
      new[] { typeof(object), typeof(NotifyCollectionChangedEventArgs) }, null);
  }
}

После вызова метода Test() класса Example в консоли мы должны увидеть следущее:

List count: 5

Add item: 'Last'
event: PropertyChanged, property: 'Count'
event: PropertyChanged, property: 'Item[]'
event: CollectionChanged, action: 'Add'
List content: 'One,Two,Three,Four,Five,Last'

Replace item: 'ThreeNew' at position: '2'
event: PropertyChanged, property: 'Item[]'
event: CollectionChanged, action: 'Replace'
Previous item: 'Three'
List content: 'One,Two,ThreeNew,Four,Five,Last'

Move item from position: '0' to position: '3'
event: PropertyChanged, property: 'Item[]'
event: CollectionChanged, action: 'Move'
List content: 'Two,ThreeNew,Four,One,Five,Last'

Raise custom PropertyChanged event: 'MyProperty'
event: PropertyChanged, property: 'MyProperty'

В итоге, отмечу, что nuget-пакет в котором содержится класс Reflector называется VasEug.PowerLib.System и имеет MIT лицензию.

Теги:
Хабы:
Всего голосов 4: ↑1 и ↓3-2
Комментарии7

Публикации

Истории

Работа

Ближайшие события

19 августа – 20 октября
RuCode.Финал. Чемпионат по алгоритмическому программированию и ИИ
МоскваНижний НовгородЕкатеринбургСтавропольНовосибрискКалининградПермьВладивостокЧитаКраснорскТомскИжевскПетрозаводскКазаньКурскТюменьВолгоградУфаМурманскБишкекСочиУльяновскСаратовИркутскДолгопрудныйОнлайн
24 – 25 октября
One Day Offer для AQA Engineer и Developers
Онлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
26 октября
ProIT Network Fest
Санкт-Петербург
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань