CodeGym /Kursy /C# SELF /Refleksja: System.Reflecti...

Refleksja: System.Reflection

C# SELF
Poziom 63 , Lekcja 0
Dostępny

1. Wprowadzenie

Refleksja (od ang. Reflection) — to możliwość twojego programu, żeby badać własną strukturę w czasie wykonywania. Wyobraź sobie, że program może zapytać sam siebie: 'Jakie mam metody? Jakie właściwości ma ten typ?' i otrzymać odpowiedź w runtime.

Przykłady zastosowania refleksji w praktyce

  • Tworzenie pluginów i modułów: Nie musisz z góry wiedzieć, z jaką klasą będziesz pracować — wystarczy załadować DLL i poznać jej metody w locie.
  • Automatyzacja testów: Frameworki testowe, np. xUnit i NUnit, znajdują metody testowe przez refleksję.
  • Serializacja: Dzięki refleksji możesz automatycznie zamienić dowolny obiekt na JSON/XML bez ręcznego iterowania po właściwościach.
  • Walidacja danych przez atrybuty: Na przykład dodałeś atrybut [Required] do właściwości — i chcesz automatycznie sprawdzić wszystkie takie właściwości.

Dlaczego warto to znać na rozmowach kwalifikacyjnych

Umiejętność pracy z refleksją często pojawia się na rozmowach, bo pokazuje twoje zrozumienie wnętrza .NET i zdolność rozwiązywania zadań o charakterze dynamicznym. Jeśli chcesz pisać własne frameworki lub rozszerzenia, refleksja jest nieoceniona.

2. Przestrzeń nazw i podstawowe klasy refleksji

Cała funkcjonalność refleksji w C# jest dostępna przez przestrzeń nazw System.Reflection. Na początku pliku nie zapomnij:

using System.Reflection;

Główne typy refleksji

Klasa/Typ Opis
Type
Reprezentuje opis typu w .NET (np. klasa, interfejs, enum itd.)
PropertyInfo
Właściwość typu
MethodInfo
Metoda typu
FieldInfo
Pole typu
ConstructorInfo
Konstruktor typu
Assembly
Reprezentuje assembly (EXE lub DLL)

Podstawowe metody klasy Type

Metoda Opis
GetProperties()
Zwraca wszystkie publiczne właściwości
GetFields()
Zwraca wszystkie publiczne pola
GetMethods()
Zwraca wszystkie publiczne metody
GetConstructors()
Zwraca wszystkie publiczne konstruktory
GetInterfaces()
Zwraca wszystkie interfejsy, które implementuje typ
GetCustomAttributes()
Zwraca wszystkie atrybuty zastosowane do typu

Często do tych metod możesz przekazać BindingFlags by uzyskać dostęp do prywatnych lub statycznych członków, ale na razie ograniczymy się do podstawowego scenariusza.

3. Pierwszy krok: pobranie obiektu Type

Wszystkie techniki refleksji opierają się na pracy z obiektem typu Type.

Istnieje kilka sposobów, żeby dostać ten obiekt, przejrzyjmy każdy:

Sposób 1: Przez obiekt

string text = "Hello, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String

Sposób 2: Przez nazwę typu

Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32

Sposób 3: Po stringu (dynamicznie)

Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double

Uważaj! Dla własnych klas z innych assembly trzeba podać pełną nazwę typu wraz z assembly.

4. Badamy strukturę typu: właściwości, metody, pola i konstruktory

Nadszedł moment prawdy: dostaliśmy obiekt Type — i teraz możemy dowiedzieć się wszystkiego, co chcesz.

Pobieranie listy właściwości

Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();

foreach (var prop in properties)
{
    Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}

Wyjście:

Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...

Pobieranie listy metod

MethodInfo[] methods = type.GetMethods();

foreach (var method in methods)
{
    Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}

Duże typy mogą mieć wiele metod! Często potrzebujesz nie wszystkich, a tylko "swoich" (np. z wyłączeniem metod odziedziczonych z object). Zajmiemy się tym później.

Pobieranie listy pól

FieldInfo[] fields = type.GetFields();

foreach (var field in fields)
{
    Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}

Ale większość zwykłych klas nie ma publicznych pól, bo enkapsulacja to nasze wszystko :)

Pobieranie konstruktorów

ConstructorInfo[] constructors = type.GetConstructors();

foreach (var ctor in constructors)
{
    Console.WriteLine($"Konstruktor: {ctor}");
}

5. Stosujemy refleksję w praktyce: analiza naszej aplikacji

Przypomnę, że w naszym projekcie edukacyjnym jest klasa TaskItem, w której przechowujemy zadania. Zbadajmy ją przy pomocy refleksji.

public class TaskItem
{
    public int Id { get; set; }
    public string Title { get; set; }
    public DateTime DueDate { get; set; }
    public bool IsCompleted;
}

Tak można poznać całą "zawartość" naszego typu w runtime:

Type taskType = typeof(TaskItem);

Console.WriteLine("Właściwości:");
foreach (var prop in taskType.GetProperties())
    Console.WriteLine($"  {prop.PropertyType.Name} {prop.Name}");

Console.WriteLine("Pola:");
foreach (var field in taskType.GetFields())
    Console.WriteLine($"  {field.FieldType.Name} {field.Name}");

Console.WriteLine("Metody:");
foreach (var method in taskType.GetMethods())
    Console.WriteLine($"  {method.ReturnType.Name} {method.Name}()");

Spróbuj dodać do klasy nowe właściwości i zobaczyć, jak refleksja je wykryje. Tak działają uniwersalne serializery i inspektory obiektów.

6. Wizualizacja: jak zbudowany jest obiekt Type (schemat)


+-------------------------+
|         Type            |
+-------------------------+
|  .Name                  |
|  .FullName              |
|  .Namespace             |
|  .Assembly              |
+-------------------------+
|  .GetProperties()       |
|  .GetFields()           |
|  .GetMethods()          |
|  .GetConstructors()     |
|  .GetInterfaces()       |
|  .GetCustomAttributes() |
+-------------------------+

Tak wygląda nasz "portret typu" — ma nazwę, assembly, zestaw członków i atrybuty.

Pobieranie podstawowych informacji o typie

Obiekt Type może powiedzieć, czym jest dany typ:

Type t = typeof(double);

Console.WriteLine(t.Name);         // Double
Console.WriteLine(t.FullName);     // System.Double
Console.WriteLine(t.Namespace);    // System
Console.WriteLine(t.IsClass);      // False
Console.WriteLine(t.IsValueType);  // True
Console.WriteLine(t.IsEnum);       // False
Console.WriteLine(t.IsPrimitive);  // True

7. Wyszukiwanie konkretnej właściwości, metody lub pola po nazwie

Właściwość

var prop = taskType.GetProperty("Title");
if (prop != null)
{
    Console.WriteLine($"Typ właściwości 'Title': {prop.PropertyType}");
}

Metoda

var method = taskType.GetMethod("ToString");
if (method != null)
{
    Console.WriteLine($"Metoda 'ToString' zwraca: {method.ReturnType}");
}

Pole

var field = taskType.GetField("IsCompleted");
if (field != null)
{
    Console.WriteLine($"Typ pola 'IsCompleted': {field.FieldType}");
}

8. Używamy refleksji do dynamicznego dostępu do wartości

Teraz nie tylko patrzymy, ale i dotykamy wartości!

Odczyt wartości właściwości

TaskItem item = new TaskItem { Id = 1, Title = "Przetestować refleksję", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");

if (titleProp != null)
{
    object value = titleProp.GetValue(item);
    Console.WriteLine($"Tytuł: {value}");
}

Ustawienie wartości właściwości

titleProp.SetValue(item, "Zmodyfikowany tytuł");
Console.WriteLine(item.Title);

Praca z polami

FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true

Dla prywatnych pól i właściwości potrzebne są specjalne flagi, np. BindingFlags.NonPublic. O tym opowiem później.

9. Wywoływanie metod przez refleksję

Możesz wywoływać metody, nawet nie znając ich w czasie pisania kodu!

TaskItem task = new TaskItem { Title = "Refleksja — super!" };
MethodInfo method = task.GetType().GetMethod("ToString");

if (method != null)
{
    object result = method.Invoke(task, null);
    Console.WriteLine(result);
}

Jeśli metoda ma parametry, trzeba je przekazać jako tablicę obiektów:

public class Calculator
{
    public int Add(int a, int b) => a + b;
}

var calc = new Calculator();
var addMethod = typeof(Calculator).GetMethod("Add");
object[] parameters = { 5, 3 };
object sumResult = addMethod.Invoke(calc, parameters);
Console.WriteLine(sumResult); // 8

10. Dynamiczne tworzenie obiektów przez konstruktor

Refleksja pozwala tworzyć obiekty nawet bez operatora new!

Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// To ten sam TaskItem (ale typ — object)
Console.WriteLine(newTask.GetType().Name); // TaskItem

Można przekazać parametry konstruktora:

public class User
{
    public string Name { get; }
    public User(string name) { Name = name; }
}

Type userType = typeof(User);
object user = Activator.CreateInstance(userType, "Elis");
Console.WriteLine(((User)user).Name); // Elis

11. Typowe błędy przy pracy z System.Reflection

Błąd nr 1: Odwoływanie się do prywatnych członków bez BindingFlags.
Domyślnie refleksja widzi tylko publiczne członki. Bez BindingFlags.NonPublic dostęp do prywatnych pól lub metod jest niemożliwy.

Błąd nr 2: Ignorowanie sprawdzenia na null.
Metody GetProperty, GetMethod i inne mogą zwrócić null, jeśli członek nie zostanie znaleziony. Bez sprawdzenia to doprowadzi do NullReferenceException.

Błąd nr 3: Nieprawidłowy wybór przeciążenia metody.
Przy wielu przeciążeniach metoda GetMethod może zwrócić niewłaściwą wersję. Używaj GetMethod z podaniem typów parametrów.

Błąd nr 4: Używanie refleksji w kodzie krytycznym wydajnościowo.
Refleksja jest wolniejsza niż bezpośrednie wywołania. Dla kodu wysokowydajnego używaj cache'owania metadanych lub delegatów.

2
Zadanie
C# SELF, poziom 63, lekcja 0
Niedostępne
Pobieranie listy właściwości, metod i pól typu
Pobieranie listy właściwości, metod i pól typu
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION