Wprowadzenie do refleksji

Refleksja w C# jest zaawansowaną funkcją umożliwiającą uzyskiwanie informacji o typach (strukturach danych takich jak klasy, interfejsy oraz ich składowych: właściwościach i metodach) oraz dynamiczne tworzenie obiektów, odczytywanie i modyfikowanie ich danych, a także wywoływanie ich metod w czasie działania programu [1, 2].

System.Reflection to przestrzeń nazw w .NET, która zawiera wszystkie klasy potrzebne do korzystania z mechanizmu refleksji. W tabeli poniżej przedstawiono kilka z nich wraz z definicjami z oficjalnej dokumentacji [3].

KlasaZastosowanie
TypeInfoReprezentuje deklaracje typu dla typów klas, typów interfejsów, typów tablic, typów wartości, typów wyliczenia, parametrów typu, ogólnych definicji typów i otwartych lub zamkniętych skonstruowanych typów ogólnych.
PropertyInfoOdnajduje atrybuty właściwości i zapewnia dostęp do metadanych właściwości.
MethodInfoOdnajduje atrybuty metody i zapewnia dostęp do metadanych metody.
FieldInfoOdnajduje atrybuty pola i zapewnia dostęp do metadanych pola.
ConstructorInfoOdnajduje atrybuty konstruktora klasy i zapewnia dostęp do metadanych konstruktora.
AssemblyReprezentuje zestaw, który jest zestawem wielokrotnego użytku, wersjonalnym i samoopisującym blok konstrukcyjny aplikacji środowiska uruchomieniowego języka wspólnego.

TypeInfo służy do odczytywania podstawowych metadanych typu. Klasa ta udostępnia m.in. kolekcje takie jak DeclaredProperties, DeclaredMethods, DeclaredFields i DeclaredConstructors, które zawiertają odpowiednio obiekty PropertyInfo, MethodInfo, FieldInfo i ConstructorInfo [4]. Praktyczny przykład przedstawiający wykorzystanie obiektu TypeInfo znajduje się poniżej.

internal class Program
{
    static void Main(string[] args)
    {
        TypeInfo info = typeof(Book).GetTypeInfo();

        Console.WriteLine("Class Information\n");

        Console.WriteLine("Properties:");
        foreach (var prop in info.DeclaredProperties)
        {
            Console.WriteLine($"- {prop.Name} ({prop.PropertyType.Name})");
        }

        Console.WriteLine("\nMethods:");
        foreach (var metoda in info.DeclaredMethods)
        {
            Console.WriteLine($"- {metoda.Name}");
        }

        Console.WriteLine("\nFields:");
        foreach (var field in info.DeclaredFields)
        {
            Console.WriteLine($"- {field.Name} ({field.FieldType.Name})");
        }

        Console.WriteLine("\nConstructors:");
        foreach (var ctor in info.DeclaredConstructors)
        {
            Console.WriteLine($"- {ctor.Name} z {ctor.GetParameters().Length} parametrami");
        }
    }
}

internal class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public int Year { get; set; }
    public void DisplayInfo()
    {
        Console.WriteLine($"Title: {Title}, Author: {Author}, Year: {Year}");
    }
}

Wykorzystanie refleksji pozwala na dynamiczne tworzenie obiektów na dwa sposoby. Pierwszym z nich jest zastosowanie metody Activator.CreateInstance [5]. Natomiast drugim podejściem jest wywołanie konstruktora typu poprzez obiekt ConstructorInfo i jego metodę Invoke [6]. Stworzone w ten sposób obiekty działają w taki sam sposób jak te utworzone tradycyjnie. Oba sposoby zostały zaprezentowane w poniższym kodzie.

using System.Reflection;

namespace Reflection
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Type typ = typeof(Book);

            // Use Activator to create an instance
            object obj1 = Activator.CreateInstance(typ);
            Book bookCreatedByActivator = (Book)obj1;
            bookCreatedByActivator.Title = "Krzyżacy";
            bookCreatedByActivator.Author = "Henryk Sienkiewicz";
            bookCreatedByActivator.Year = 1899;
            bookCreatedByActivator.DisplayInfo();

            // Use ConstructorInfo to create an instance
            ConstructorInfo ctor = typ.GetConstructor(Type.EmptyTypes);
            object obj2 = ctor.Invoke(null);
            Book bookCreatedByConstructorInfoInvoke = (Book)obj2;
            bookCreatedByConstructorInfoInvoke.Title = "Pan Tadeusz";
            bookCreatedByConstructorInfoInvoke.Author = "Adam Mickiewicz";
            bookCreatedByConstructorInfoInvoke.Year = 1834;
            bookCreatedByConstructorInfoInvoke.DisplayInfo();
        }
    }

    internal class Book
    {
        public string Title { get; set; }
        public string Author { get; set; }
        public int Year { get; set; }

        
        public void DisplayInfo()
        {
            Console.WriteLine($"Title: {Title}, Author: {Author}, Year: {Year}");
        }
    }
}

Refleksja pozwala również na dynamiczne ładowanie i obsługę zewnętrznych bibliotek DLL za pomocą klasy Assembly [7]. Po załadowaniu biblioteki można pobrać interesujący typ, utworzyć jego instancję i korzystać z klas i metod zdefiniowanych w pliku DLL tak jakby od początku był częścią projektu. Najlepiej zobaczyć to na przykładzie kodu dwóch projektów poniżej (biblioteki i programu konsolowego).

namespace Library
{
    public class ClassInDLL
    {
        public int Add(int a, int b)
        {
            return a + b;
        }
    }
}
using System.Reflection;

namespace Reflection
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Assembly assembly = Assembly.LoadFrom("Library.dll");
            Type type = assembly.GetType("Library.ClassInDLL");
            object calc = Activator.CreateInstance(type);
            MethodInfo method = type.GetMethod("Add");
            object result = method.Invoke(calc, new object[] { 5, 7 });
            Console.WriteLine($"Result: {result}");
        }
    }
}

[1] https://medium.com/@ravipatel.it/understanding-c-reflection-a-beginners-guide-28d17388a79c

[2] https://www.c-sharpcorner.com/UploadFile/009ee3/reflection-in-C-Sharp/

[3] https://learn.microsoft.com/pl-pl/dotnet/api/system.reflection?view=net-8.0

[4] https://learn.microsoft.com/pl-pl/dotnet/api/system.reflection.typeinfo?view=net-8.0

[5] https://learn.microsoft.com/pl-pl/dotnet/api/system.activator.createinstance?view=net-8.0

[6] https://learn.microsoft.com/pl-pl/dotnet/api/system.reflection.constructorinfo.invoke?view=net-8.0

[7] https://learn.microsoft.com/pl-pl/dotnet/api/system.reflection.assembly?view=net-8.0

Dodaj komentarz