Cześć! Dziś opowiem o refleksji (Reflection) w C#. To potężny mechanizm pozwalający na podejrzenie (i w pewnym stopniu modyfikację) kodu w czasie wykonywania. Dzięki refleksji możemy m.in. dynamicznie tworzyć obiekty na podstawie nazw klas, wywoływać metody, odczytywać atrybuty i wiele więcej.
Czym jest refleksja w .NET?
Refleksja pozwala na badanie i manipulowanie metadanymi typów w czasie wykonywania aplikacji. Oznacza to, że możemy np.:
• Odpytać klasę o jej konstruktor, metody, właściwości, pola czy atrybuty.
• Utworzyć obiekt klasy bezpośrednio ze stringa zawierającego jej pełną nazwę.
• Wywołać metodę, nawet jeśli normalnie nie mielibyśmy do niej dostępu wprost.
W praktyce najczęściej wykorzystywane są przestrzenie nazw z rodziny System.Reflection, np. Type, MethodInfo, PropertyInfo.
Podstawowy przykład: Pobieranie informacji o typie
Załóżmy, że mamy prostą klasę:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public void Introduce()
{
Console.WriteLine($"Cześć, jestem {FirstName} {LastName}.");
}
}
W kodzie aplikacji możemy skorzystać z refleksji, by pobrać informacje o właściwościach i metodach klasy Person:
using System;
using System.Reflection;
public class ReflectionDemo
{
public static void Main(string[] args)
{
// Uzyskujemy obiekt typu dla klasy Person
Type personType = typeof(Person);
// Wypisujemy nazwy właściwości
Console.WriteLine("Właściwości klasy Person:");
foreach (PropertyInfo propInfo in personType.GetProperties())
{
Console.WriteLine($"- {propInfo.Name} ({propInfo.PropertyType})");
}
// Wypisujemy nazwy metod
Console.WriteLine("\nMetody klasy Person:");
foreach (MethodInfo methodInfo in personType.GetMethods())
{
// Filtrujemy tylko te metody, które zdefiniowaliśmy (pomijając wbudowane z System.Object)
if (methodInfo.DeclaringType == typeof(Person))
{
Console.WriteLine($"- {methodInfo.Name}");
}
}
}
}
Po uruchomieniu zobaczysz w konsoli m.in. nazwy właściwości FirstName, LastName i metodę Introduce.
Tworzenie obiektu dynamicznie i wywoływanie metod
Możemy nie tylko "podejrzeć" definicję klasy, ale też utworzyć jej instancję i wywołać na niej metodę, nie używając operatora new w tradycyjny sposób.
public class ReflectionInstantiationDemo
{
public static void Main(string[] args)
{
// Pobieramy typ Person
Type personType = typeof(Person);
// Tworzymy instancję klasy Person przy pomocy Activator
object personInstance = Activator.CreateInstance(personType);
// Ustawiamy właściwości
var firstNameProperty = personType.GetProperty("FirstName");
var lastNameProperty = personType.GetProperty("LastName");
firstNameProperty.SetValue(personInstance, "Jan");
lastNameProperty.SetValue(personInstance, "Kowalski");
// Wywołujemy metodę Introduce
var introduceMethod = personType.GetMethod("Introduce");
introduceMethod.Invoke(personInstance, null);
// Efekt w konsoli: "Cześć, jestem Jan Kowalski."
}
}
Co tu się dzieje?
• Activator.CreateInstance(personType) tworzy nowy obiekt klasy Person, ale w sposób dynamiczny, bazując na typie (w runtime).
• Metoda SetValue() pozwala ustawić wartość właściwości, nie mając w kodzie bezpośrednio personInstance.FirstName = "Jan".
• Invoke() wywołuje metodę z podanymi parametrami (tu akurat null, bo Introduce() nie przyjmuje żadnych argumentów).
Atrybuty i refleksja
Innym częstym przypadkiem użycia refleksji jest praca z atrybutami (attributes). Możemy np. tworzyć własne atrybuty, które opisują dodatkowo klasy czy właściwości, a potem w runtime odczytywać te informacje i odpowiednio sterować zachowaniem programu.
Przykładowy atrybut:
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class DisplayLabelAttribute : Attribute
{
public string Label { get; }
public DisplayLabelAttribute(string label)
{
Label = label;
}
}
Zastosowanie atrybutu:
public class Product
{
[DisplayLabel("Nazwa produktu")]
public string Name { get; set; }
[DisplayLabel("Cena")]
public decimal Price { get; set; }
}
Czytanie atrybutów refleksją:
Type productType = typeof(Product);
foreach (PropertyInfo prop in productType.GetProperties())
{
var displayAttr = prop.GetCustomAttribute<DisplayLabelAttribute>();
if (displayAttr != null)
{
Console.WriteLine($"Właściwość {prop.Name} ma etykietę: {displayAttr.Label}");
}
}
Taka technika przydaje się np. do automatycznego generowania formularzy, mapowania danych czy walidacji.
Kiedy refleksja bywa przydatna?
1. Tworzenie frameworków i bibliotek – np. automatyczne rejestrowanie klas w kontenerach IoC, skanowanie projektów w poszukiwaniu określonych typów (np. pluginów).
2. Serializacja i deserializacja – narzędzia jak JSON.NET czy XML Serializer często korzystają z refleksji.
3. Automatyczne generowanie UI – np. tworzenie formularzy w oparciu o atrybuty i nazwy właściwości.
4. Testy automatyczne – np. framework testowy może wyszukiwać metody testowe w kodzie (atrybut [TestMethod]) i je wywoływać.
Podsumowanie
Refleksja to potężny mechanizm w .NET, pozwalający na dynamiczne eksplorowanie i modyfikowanie typów w czasie działania programu. Jest nieoceniona przy pisaniu własnych bibliotek, frameworków, systemów wtyczek czy narzędzi do automatycznej generacji kodu i UI. Jednocześnie warto pamiętać, że refleksja jest bardziej zasobożerna niż zwykłe, statyczne wywołania metod – więc trzeba jej używać rozważnie.
Jeśli interesują Cię takie zaawansowane aspekty C# i .NET oraz chcesz krok po kroku przejść przez nieco bardziej rozbudowane tematy programistyczne, zapraszam Cię na moje szkolenie online: "Zostań Programistą .NET". Poznasz w nim nie tylko refleksję, ale też wzorce projektowe, testy, pracę z bazami danych i wiele więcej.
Daj znać w komentarzu, w jaki sposób najchętniej wykorzystujesz refleksję lub co najbardziej Cię w niej zaskakuje!
To wszystkie na dzisiaj. Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności – darmowe zapisy, gdzie będziesz również miał dostęp do dodatkowych materiałów i przede wszystkim bonusów. Do zobaczenia w kolejnym artykule.