1. Wprowadzenie
W C# każda klasa (lub struct) — to zbiór członków. Należą do nich:
- Pola (fields): zmienne trzymające dane.
- Właściwości (properties): “sprytne” opakowania z get/set.
- Metody (methods): funkcjonalność.
- Konstruktory (constructors): sposoby utworzyć instancję.
- Zdarzenia (events): publish-subscribe w praktyce.
Wyobraź sobie, że masz magiczną lupę, dzięki której możesz zajrzeć do wnętrza dowolnego typu, poznać jego zawartość i nawet ją zmienić. To właśnie refleksja.
Przestrzeń nazw i podstawowe typy refleksji
Zanim zaczniemy, nie zapomnij dodać przestrzeni nazw:
using System.Reflection;
Kluczowe klasy i interfejsy:
| Klasa/interfejs | Opis |
|---|---|
|
Główne wejście — wszystko zaczyna się od niego |
|
Informacja o polu |
|
Informacja o właściwości |
|
Informacja o metodzie |
|
Informacja o konstruktorze |
|
Informacja o zdarzeniu |
|
Wspólny “rodzic” dla wszystkich powyżej |
2. Pobieramy członków typu przez refleksję
Pobieramy pola i właściwości
Przykład: enumeracja pól i właściwości
Załóżmy, że mamy taką klasę:
public class Person
{
public string Name { get; set; }
public int Age;
private string Secret = "Sekretne słowo";
}
Pobieramy pola:
Type personType = typeof(Person);
FieldInfo[] fields = personType.GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
Console.WriteLine($"Pole: {field.Name}, Typ: {field.FieldType.Name}, Dostęp: {field.Attributes}");
}
Pobieramy właściwości:
PropertyInfo[] properties = personType.GetProperties(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var property in properties)
{
Console.WriteLine($"Właściwość: {property.Name}, Typ: {property.PropertyType.Name}");
}
Wyjaśnienia
- BindingFlags — to zestaw “flag”, które mówią, jakie członki szukać (public/private, instance/static itd.).
- Pola i właściwości się różnią: pole — kawałek danych, właściwość — metody get/set, ukrywające pole.
Pobieramy metody
MethodInfo[] methods = personType.GetMethods(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
Console.WriteLine($"Metoda: {method.Name}");
}
Będziesz zdziwiony, ile metod się pojawi. Będą tam gettery/settery właściwości i pomocnicze z object!
Przydatny filtr: żeby dostać tylko twoje “jawne” metody, możesz dodać filtrowanie po nazwie lub atrybucie.
Co szukać z jakimi BindingFlags
| Co szukamy | Przykład wywołania | Opis |
|---|---|---|
| Publiczne pola | |
Wszystko, co widoczne innym |
| Prywatne pola | |
Schowane wewnętrzne |
| Tylko instancja | |
Tylko niestatyczne |
| Tylko static | |
Tylko statyczne |
| Wszystko naraz | |
Pełna lista |
3. Dynamiczne tworzenie instancji typu
Dlaczego to fajne?
Można stworzyć obiekt, nie znając jego dokładnego typu w czasie kompilacji.
Prosty sposób — przez Activator.CreateInstance
// Pobieramy Type w jakiś sposób, np.:
Type myType = typeof(Person);
// Tworzymy obiekt:
object obj = Activator.CreateInstance(myType);
// Teraz obj — to instancja Person, ale jako object
Jeśli klasa nie ma konstruktora bez parametrów, możesz przekazać parametry:
public class Animal
{
public string Name { get; }
public Animal(string name) { Name = name; }
}
// ...
Type animalType = typeof(Animal);
object dog = Activator.CreateInstance(animalType, "Szarik");
Ważne!
Console.WriteLine(dog.GetType().Name); // Animal
Bardziej niestandardowy sposób — przez ConstructorInfo
ConstructorInfo ctor = animalType.GetConstructor(new Type[] { typeof(string) });
object dog = ctor.Invoke(new object[] { "Szarik" });
W praktyce zwykle używa się Activator, ale czasem potrzebne są dokładne parametry albo praca z prywatnymi konstruktorami — wtedy ConstructorInfo pomaga.
4. Dostęp do pól i właściwości po nazwie
Pobieramy i zmieniamy pole
Person p = new Person();
Type t = p.GetType();
FieldInfo field = t.GetField("Age");
field.SetValue(p, 42);
Console.WriteLine(field.GetValue(p)); // 42
Praca z prywatnymi polami:
FieldInfo secret = t.GetField("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
if (secret != null)
{
secret.SetValue(p, "Oj, sekret ujawniony");
Console.WriteLine(secret.GetValue(p));
}
else
{
Console.WriteLine("Pole Secret nie znalezione");
}
Uwaga! Nie nadużywaj dostępu do prywatnych pól — to łamanie enkapsulacji i potencjalne bugi.
Pobieramy i zmieniamy właściwości
PropertyInfo pi = t.GetProperty("Name");
pi.SetValue(p, "Wasia");
Console.WriteLine(pi.GetValue(p)); // Wasia
Właściwości — to nie pola, a metody get/set w tle.
Dlaczego czasem nie działa?
Jeśli twoje pole lub właściwość jest prywatna, koniecznie dodaj flagi BindingFlags z odpowiednimi wartościami (np. NonPublic). Jeśli właściwość nie ma settera, nie dasz rady nic do niej zapisać przez refleksję — zasada obowiązuje.
5. Wywoływanie metod po nazwie
Wywołanie prostej metody bez parametrów
public class Greeter
{
public void SayHello()
{
Console.WriteLine("Cześć!");
}
}
Greeter g = new Greeter();
Type t = g.GetType();
MethodInfo mi = t.GetMethod("SayHello");
mi.Invoke(g, null); // Cześć!
Wywołanie metody z parametrami
public class MathOp
{
public int Add(int x, int y) => x + y;
}
MathOp calc = new MathOp();
Type t = calc.GetType();
MethodInfo mi = t.GetMethod("Add");
object result = mi.Invoke(calc, new object[] { 7, 5 });
Console.WriteLine(result); // 12
Jeśli metoda jest statyczna, w pierwszy argument Invoke przekaż null.
Praca z przeciążonymi metodami
MethodInfo mi = t.GetMethod(
"Add",
new Type[] { typeof(int), typeof(int) }
);
Albo iteruj i filtruj ręcznie:
foreach(var method in t.GetMethods()) {
if (method.Name == "Add" && method.GetParameters().Length == 2)
{
// ... nasza metoda!
}
}
6. Przydatne niuanse
Główne członki, metody i właściwości do analizy typu
| Członek klasy | Metoda do pobrania info | Klasa wyniku | Jak czytać/zmieniać wartość |
|---|---|---|---|
| Pole (field) | |
|
|
| Właściwość (prop) | |
|
|
| Metoda (method) | |
|
|
| Konstruktor | |
|
|
Po co to w praktyce?
- W ORM-ach (np. Entity Framework buduje zapytania SQL po twoich modelach).
- W systemach serializacji i parserach (np. JSON-serializery).
- W uniwersalnych bibliotekach do logowania, testowania, generatorów UI.
- Na rozmowach kwalifikacyjnych — lubią pytać „jak napisałbyś uniwersalny kopier obiektu?”.
- Do pisania pluginów, kiedy twoja aplikacja ładuje i wywołuje kod napisany przez innych.
Nawet jeśli teraz wydaje się to magią z filmów o hakerach, uwierz — przyda się!
7. Typowe błędy i cechy
Bardzo często devy mylą się z BindingFlags — nie widzą prywatnych pól albo, przeciwnie, dostają za dużo metod. Zawsze sprawdzaj kombinację flag: dla prywatnych pól użyj NonPublic, dla statycznych — Static itd.
Przy wywoływaniu metod przez Invoke przekazuj argumenty w tej samej kolejności i typie co w deklaracji. Zły porządek/typ spowoduje TargetParameterCountException lub ArgumentException. Wynik zwracany przychodzi jako object — nie zapomnij rzutować typu (albo użyć pattern matching).
Pamiętaj o wydajności: refleksja jest wolniejsza niż bezpośrednie wywołania. W gorących miejscach cache'uj znalezione Type/MethodInfo/PropertyInfo lub z góry generuj delegaty.
Często zobaczysz pomocnicze metody typu get_PropertyName i set_PropertyName. To metody dostępowe do właściwości — nie trzeba ich tworzyć ręcznie.
Jeśli zmieniasz prywatne pola lub właściwości — łamiesz enkapsulację. Niewłaściwe użycie może prowadzić do trudnych do znalezienia błędów. Jak mówił wujek Ben: „Z wielką mocą wiąże się wielka odpowiedzialność” — używaj ostrożnie, zwłaszcza w bibliotekach.
GO TO FULL VERSION