1. Einführung
Reflection (von engl. Reflection) ist die Möglichkeit deines Programms, seine eigene Struktur zur Laufzeit zu untersuchen. Stell dir vor, dein Programm könnte sich selbst fragen: 'Welche Methoden habe ich? Welche Eigenschaften hat dieser Typ?' und zur Laufzeit eine Antwort bekommen.
Beispiele für den Einsatz von Reflection in der Praxis
- Plugins und Module laden: Du musst nicht vorher wissen, mit welcher Klasse du arbeiten wirst — du kannst eine DLL laden und ihre Methoden zur Laufzeit herausfinden.
- Automatisiertes Testen: Testframeworks wie xUnit und NUnit finden Testmethoden über Reflection.
- Serialisierung: Mit Reflection kannst du jedes Objekt automatisch in JSON/XML verwandeln, ohne die Eigenschaften manuell abzuarbeiten.
- Validierung per Attribute: Zum Beispiel hast du das Attribut [Required] an eine Eigenschaft gehängt — und willst automatisch alle solchen Eigenschaften prüfen.
Warum das bei Vorstellungsgesprächen wichtig ist
Die Fähigkeit, mit Reflection zu arbeiten, wird oft in Interviews gefragt, weil sie dein Verständnis von .NET zeigt und die Fähigkeit, dynamische Probleme zu lösen. Wenn du eigene Frameworks oder Erweiterungen schreiben willst, ist Reflection unverzichtbar.
2. Namespace und grundlegende Reflection-Klassen
Die gesamte Reflection-Funktionalität in C# ist über den Namespace System.Reflection verfügbar. Vergiss am Anfang der Datei nicht:
using System.Reflection;
Wichtige Reflection-Typen
| Klasse/Typ | Beschreibung |
|---|---|
|
Stellt die Beschreibung eines Typs in .NET dar (z.B. Klasse, Interface, enum usw.) |
|
Eigenschaft eines Typs |
|
Methode eines Typs |
|
Feld eines Typs |
|
Konstruktor eines Typs |
|
Stellt eine Assembly (EXE oder DLL) dar |
Wichtige Methoden der Klasse Type
| Methode | Beschreibung |
|---|---|
|
Gibt alle öffentlichen Eigenschaften zurück |
|
Gibt alle öffentlichen Felder zurück |
|
Gibt alle öffentlichen Methoden zurück |
|
Gibt alle öffentlichen Konstruktoren zurück |
|
Gibt alle Interfaces zurück, die der Typ implementiert |
|
Gibt alle auf den Typ angewandten Attribute zurück |
Oft kann man diesen Methoden BindingFlags übergeben, um auf private oder statische Mitglieder zuzugreifen, aber zunächst bleiben wir beim Basis-Szenario.
3. Der allererste Schritt: ein Type-Objekt bekommen
Alle Reflection-Techniken basieren auf dem Arbeiten mit einem Type-Objekt.
Es gibt mehrere Wege, dieses Objekt zu bekommen, schauen wir uns jeden an:
Methode 1: Über ein Objekt
string text = "Hello, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String
Methode 2: Über den Typnamen
Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32
Methode 3: Per String (dynamisch)
Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double
Achte darauf! Für benutzerdefinierte Klassen aus anderen Assemblies musst du den vollständigen Typnamen inklusive Assembly angeben.
4. Struktur eines Typs untersuchen: Eigenschaften, Methoden, Felder und Konstruktoren
Jetzt kommt der spannende Teil: wir haben ein Type-Objekt — und können alles herausfinden, was wir wollen.
Eigenschaften auflisten
Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}
Ausgabe:
Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...
Methoden auflisten
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}
Bei großen Typen können sehr viele Methoden vorhanden sein! Häufig braucht man nicht alle, sondern nur die "eigenen" (z.B. ohne die von object geerbten Methoden). Dazu kommen wir später.
Felder auflisten
FieldInfo[] fields = type.GetFields();
foreach (var field in fields)
{
Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}
Die meisten normalen Klassen haben keine öffentlichen Felder, denn Kapselung ist wichtig :)
Konstruktoren auflisten
ConstructorInfo[] constructors = type.GetConstructors();
foreach (var ctor in constructors)
{
Console.WriteLine($"Konstruktor: {ctor}");
}
5. Reflection praktisch anwenden: Analyse unserer Anwendung
Zur Erinnerung: in unserem Übungsprojekt gibt es die Klasse TaskItem, in der wir Aufgaben speichern. Schauen wir sie uns per Reflection an.
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DueDate { get; set; }
public bool IsCompleted;
}
So kannst du die komplette "Innenseite" unseres Typs zur Laufzeit sehen:
Type taskType = typeof(TaskItem);
Console.WriteLine("Eigenschaften:");
foreach (var prop in taskType.GetProperties())
Console.WriteLine($" {prop.PropertyType.Name} {prop.Name}");
Console.WriteLine("Felder:");
foreach (var field in taskType.GetFields())
Console.WriteLine($" {field.FieldType.Name} {field.Name}");
Console.WriteLine("Methoden:");
foreach (var method in taskType.GetMethods())
Console.WriteLine($" {method.ReturnType.Name} {method.Name}()");
Probier aus, neue Eigenschaften zur Klasse hinzuzufügen und zu sehen, wie Reflection sie erkennt. So funktionieren universelle Serializers und Objekt-Inspector.
6. Visualisierung: wie ein Type-Objekt aufgebaut ist (Schema)
+-------------------------+
| Type |
+-------------------------+
| .Name |
| .FullName |
| .Namespace |
| .Assembly |
+-------------------------+
| .GetProperties() |
| .GetFields() |
| .GetMethods() |
| .GetConstructors() |
| .GetInterfaces() |
| .GetCustomAttributes() |
+-------------------------+
So sieht unser "Typenportrait" aus — es hat einen Namen, eine Assembly, Mitglieder und Attribute.
Basisinformationen über einen Typ bekommen
Das Type-Objekt kann Auskunft darüber geben, was für ein Typ das ist:
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. Ein bestimmtes Property, eine Methode oder ein Feld per Name finden
Property
var prop = taskType.GetProperty("Title");
if (prop != null)
{
Console.WriteLine($"Typ der Eigenschaft 'Title': {prop.PropertyType}");
}
Methode
var method = taskType.GetMethod("ToString");
if (method != null)
{
Console.WriteLine($"Methode 'ToString' gibt zurück: {method.ReturnType}");
}
Feld
var field = taskType.GetField("IsCompleted");
if (field != null)
{
Console.WriteLine($"Typ des Feldes 'IsCompleted': {field.FieldType}");
}
8. Reflection für dynamischen Zugriff auf Werte verwenden
Jetzt schauen wir uns nicht nur an, sondern greifen an die Werte!
Wert einer Eigenschaft lesen
TaskItem item = new TaskItem { Id = 1, Title = "Probiere Reflection aus", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");
if (titleProp != null)
{
object value = titleProp.GetValue(item);
Console.WriteLine($"Titel: {value}");
}
Wert einer Eigenschaft setzen
titleProp.SetValue(item, "Geänderter Titel");
Console.WriteLine(item.Title);
Mit Feldern arbeiten
FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true
Für private Felder und Eigenschaften brauchst du spezielle Flags, z.B. BindingFlags.NonPublic. Dazu später mehr.
9. Methoden per Reflection aufrufen
Du kannst Methoden aufrufen, ohne sie beim Schreiben des Codes zu kennen!
TaskItem task = new TaskItem { Title = "Reflection ist cool!" };
MethodInfo method = task.GetType().GetMethod("ToString");
if (method != null)
{
object result = method.Invoke(task, null);
Console.WriteLine(result);
}
Hat die Methode Parameter, übergib sie als Objekt-Array:
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. Dynamisches Erstellen von Objekten über Konstruktor
Reflection erlaubt es, Objekte zu erstellen, ohne den new-Operator zu benutzen!
Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// Das ist dasselbe TaskItem (aber typisiert als object)
Console.WriteLine(newTask.GetType().Name); // TaskItem
Du kannst auch Konstruktorparameter übergeben:
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. Typische Fehler bei der Arbeit mit System.Reflection
Fehler Nr.1: Auf private Mitglieder zugreifen, ohne BindingFlags zu setzen.
Standardmäßig sieht Reflection nur öffentliche Mitglieder. Ohne BindingFlags.NonPublic bekommst du keinen Zugriff auf private Felder oder Methoden.
Fehler Nr.2: Ignorieren der Prüfung auf null.
Methoden wie GetProperty, GetMethod usw. können null zurückgeben, wenn das Mitglied nicht gefunden wird. Ohne Prüfung führt das zu einer NullReferenceException.
Fehler Nr.3: Falsche Überladung einer Methode wählen.
Wenn mehrere Overloads existieren, kann GetMethod den falschen zurückgeben. Verwende die Überladung von GetMethod mit Angabe der Parameter-Typen.
Fehler Nr.4: Reflection in Performance-kritischem Code verwenden.
Reflection ist langsamer als direkte Aufrufe. Für performancekritische Pfade solltest du Metadaten cachen oder Delegates verwenden.
GO TO FULL VERSION