CodeGym /Kurse /C# SELF /Reflection: System.Reflect...

Reflection: System.Reflection

C# SELF
Level 63 , Lektion 0
Verfügbar

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
Type
Stellt die Beschreibung eines Typs in .NET dar (z.B. Klasse, Interface, enum usw.)
PropertyInfo
Eigenschaft eines Typs
MethodInfo
Methode eines Typs
FieldInfo
Feld eines Typs
ConstructorInfo
Konstruktor eines Typs
Assembly
Stellt eine Assembly (EXE oder DLL) dar

Wichtige Methoden der Klasse Type

Methode Beschreibung
GetProperties()
Gibt alle öffentlichen Eigenschaften zurück
GetFields()
Gibt alle öffentlichen Felder zurück
GetMethods()
Gibt alle öffentlichen Methoden zurück
GetConstructors()
Gibt alle öffentlichen Konstruktoren zurück
GetInterfaces()
Gibt alle Interfaces zurück, die der Typ implementiert
GetCustomAttributes()
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.

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