Рефлексия (отражение) в C#. Исследование типов

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

Итак, в предыдущей части мы разобрались с тем, что из себя представляет рефлексия в C# и рассмотрели несколько вариантов получения типа System.Type для исследования типов в C#. В этом части мы более детально рассмотрим свойства и методы класса Type, используемые для изучения типов в C#.

Объект исследования

Для изучения будем использовать класс, разработанный в прошлой части:

class Person
{ 
    public string Name { get; set; }
    public string Family { get; set; }
    private byte age;
    public byte Age 
    {
        get 
        {
            return age;
        }
        set
        { if (value > 100)
                throw new Exception("Возраст человека не может быть больше 100 лет");
            else
                age = value;
                    
        }
    }
}

Получение и изучение членов класса

Для начала попробуем вывести всю информацию о типе, воспользовавшись методом GetMembers():

using System;
using System.Reflection;

namespace Reflection
{
    class Person
    { 
        public string Name { get; set; }
        public string Family { get; set; }
        private byte age;
        public byte Age 
        {
            get 
            {
                return age;
            }
            set
            { if (value > 100)
                    throw new Exception("Возраст человека не может быть больше 100 лет");
                else
                    age = value;
                        
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person user = new Person
            {
                Name = "Вася",
                Family = "Пупкин",
                Age = 18
            };


            Type type = user.GetType();
            foreach (MemberInfo info in type.GetMembers())
            {
                Console.WriteLine($"{info.Name}, {info.DeclaringType}, {info.MemberType}");
            }
        }
    }
}

В приведенном примере мы получим все общедоступные члены класса Person. Вывод консоли будет следующим:

get_Name, Reflection.Person, Method
set_Name, Reflection.Person, Method
get_Family, Reflection.Person, Method
set_Family, Reflection.Person, Method
get_Age, Reflection.Person, Method
set_Age, Reflection.Person, Method
GetType, System.Object, Method
ToString, System.Object, Method
Equals, System.Object, Method
GetHashCode, System.Object, Method
.ctor, Reflection.Person, Constructor
Name, Reflection.Person, Property
Family, Reflection.Person, Property
Age, Reflection.Person, Property

Свойство DeclaringType возвращает полное название типа.

Свойство MemberType возвращает значение из перечисления MemberTypes, в котором определены следующие типы:

  • MemberTypes.Constructor — член класса представляет собой конструктор
  • MemberTypes.Method — член класса представляет собой метод
  • MemberTypes.Field — член класса представляет собой поле
  • MemberTypes.Event — член класса представляет собой событие
  • MemberTypes.Property — член класса представляет собой свойство
  • MemberTypes.NestedType —  член класса представляет собой вложенный тип

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

Получение информации о методах

Для получения информации о методах нам могут быть полезны методы GetMethods() и GetParameters(). Рассмотрим пример использования этих методов:

Person user = new Person
{
    Name = "Вася",
    Family = "Пупкин",
    Age = 18
};


Type type = user.GetType();

foreach (MethodInfo method in type.GetMethods())
{
    string modificator = "";
    if (method.IsStatic)
        modificator += "static";
    if (method.IsVirtual)
        modificator += " virtual";
    if (method.IsAbstract)
        modificator += " abstract";

    string parameters = "";
    foreach (ParameterInfo parameter in method.GetParameters())
    {
        if (parameter.IsOut)
            parameters += "out ";
        parameters += $"{parameter.ParameterType.Name} {parameter.Name}";
    }

    Console.WriteLine($"{modificator} {method.ReturnType.Name} {method.Name}({parameters}) ");
}

Результат работы приложения:

String get_Name()
Void set_Name(String value)
String get_Family()
Void set_Family(String value)
Byte get_Age()
Void set_Age(Byte value)
Type GetType()
virtual String ToString()
virtual Boolean Equals(Object obj)
virtual Int32 GetHashCode()

Как можно видеть по результатам работы программы, мы извлекли из нашего класса вообще все методы — и методы непосредственно самого класса и унаследованные методы и виртуальные и т.д. Вместе с этим мы можем воспользоваться переопределенной версией методы GetMethods(), которая выглядит следующим образом:

MethodInfo[] GetMethods(BindingFlags)

Перечисление BindingFlags может принимать следующие значения:

  • DeclaredOnly— получает только методы непосредственно данного класса, унаследованные методы не извлекаются
  • Instance— получает только методы экземпляра
  • NonPublic— извлекает не публичные методы
  • Public— получает только публичные методы
  • Static— получает только статические методы

Объединяя значения с помощью побитовой операции ИЛИ можно комбинировать вывод. Например, получим только методы самого класса без унаследованных, как публичные, так и все остальные:

MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

foreach (MethodInfo method in methods)
{
    string modificator = "";
    if (method.IsStatic)
        modificator += "static";
    if (method.IsVirtual)
        modificator += " virtual";
    if (method.IsAbstract)
        modificator += " abstract";

    string parameters = "";
    foreach (ParameterInfo parameter in method.GetParameters())
    {
        if (parameter.IsOut)
            parameters += "out ";
        parameters += $"{parameter.ParameterType.Name} {parameter.Name}";
    }

    Console.WriteLine($"{modificator} {method.ReturnType.Name} {method.Name}({parameters}) ");
}

В этом случае вывод консоли будет намного короче:

String get_Name()
Void set_Name(String value)
String get_Family()
Void set_Family(String value)
Byte get_Age()
Void set_Age(Byte value)

Получение конструкторов

Для получения конструкторов применяется метод ConstructorInfo[] GetConstructors(). Чтобы продемонстрировать работу этого метода допишем в нашем классе свой конструктор:

class Person
{
    public Person(string name, string family, byte age)
    {
        Name = name;
        Family = family;
        Age = age;
    }
    [...]
}
Type type = user.GetType();

foreach (ConstructorInfo ctor in type.GetConstructors())
{
    Console.Write(type.Name + " (");
    // получаем параметры конструктора
    ParameterInfo[] parameters = ctor.GetParameters();
    for (int i = 0; i < parameters.Length; i++)
    {
        Console.Write(parameters[i].ParameterType.Name + " " + parameters[i].Name);
        if (i + 1 < parameters.Length) Console.Write(", ");
    }
    Console.WriteLine(")");
}

Результат работы:

Person ()
Person (String name, String family, Byte age)

Получение информации о полях и свойствах

Для извлечения полей и свойств применяются соответственно методы GetFields() и GetProperties():

Type type = user.GetType();

Console.WriteLine("Поля класса:");
foreach (FieldInfo field in type.GetFields())
{
    Console.WriteLine($"    {field.FieldType} {field.Name}");
}


Console.WriteLine("Свойства класса:");
foreach (PropertyInfo prop in type.GetProperties())
{
    Console.WriteLine($"    {prop.PropertyType} {prop.Name}");
}

Вывод консоли:

Поля класса:
Свойства класса:
System.String Name
System.String Family
System.Byte Age

Опять же, воспользовавшись простой формой метода GetFields() мы вывели только публичные поля. Чтобы получить все поля класса, мы можем, как в случае и с методами, воспользоваться переопределенной формой метода:

Console.WriteLine("Поля класса:");
foreach (FieldInfo field in type.GetFields(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance|BindingFlags.DeclaredOnly))
{
    Console.WriteLine($"    {field.FieldType} {field.Name}");
}

В этом случае, вывод консоли будет следующим:

Поля класса:
System.String k__BackingField
System.String k__BackingField
System.Byte age

Поиск реализованных интерфейсов

Чтобы получить все реализованные типом интерфейсы, надо использовать метод GetInterfaces(), который возвращает массив объектов Type:

Console.WriteLine("Реализованные интерфейсы:");
foreach (Type i in type.GetInterfaces())
{
    Console.WriteLine(i.Name);
}

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

Итого

Объект Type позволяет проводить комплексное изучение определенного типа в C# — получать публичные и закрытые экземпляры класса (методы, поля, свойства), получать список наследуемых интерфейсов, изучать параметры методов и так далее. Комбинируя различные методы и свойства Type мы можем получать полную информацию о том или ином типе C#.

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
guest
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии