CodeGym /Kursy /C# SELF /Używanie refleksji

Używanie refleksji

C# SELF
Poziom 63 , Lekcja 1
Dostępny

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
Type
Główne wejście — wszystko zaczyna się od niego
FieldInfo
Informacja o polu
PropertyInfo
Informacja o właściwości
MethodInfo
Informacja o metodzie
ConstructorInfo
Informacja o konstruktorze
EventInfo
Informacja o zdarzeniu
MemberInfo
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
GetFields(BindingFlags.Public | ...)
Wszystko, co widoczne innym
Prywatne pola
GetFields(BindingFlags.NonPublic | ...)
Schowane wewnętrzne
Tylko instancja
BindingFlags.Instance
Tylko niestatyczne
Tylko static
BindingFlags.Static
Tylko statyczne
Wszystko naraz
BindingFlags.Public | NonPublic | Instance | Static
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)
GetField, GetFields
FieldInfo
.GetValue(obj), .SetValue(obj, x)
Właściwość (prop)
GetProperty, GetProperties
PropertyInfo
.GetValue(obj), .SetValue(obj, x)
Metoda (method)
GetMethod, GetMethods
MethodInfo
.Invoke(obj, parametry)
Konstruktor
GetConstructor, GetConstructors
ConstructorInfo
.Invoke(parametry)

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.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION