Skip to Content

Fortgeschrittene Konzepte

Erweiterungsmethoden

Extension Methods sind statische Methoden, die auf Instanzen eines Typs angewendet werden können. Sie befinden sich einer statischen Klasse, sind selbst statisch und haben als ersten Parameter this und den Typen, auf den die Methode angewendet werden soll.

public static class StringExtensions { public static string Reverse(this string s) { return new string(s.Reverse().ToArray()); } } string s = "Hello".Reverse();

Stack und Heap

Im Stack befinden sich lokale Variaben und Parameter eines Methodenaufrufs. Diese werden automatisch aufgeräumt, wenn die Methode beendet wird. Typisch auf einem Stack sind Werte von Value Types und Referenzen zu Objekten.

Auf dem Heap befinden sich Objekte die zur Laufzeit erzeugt werden, wie new Person(), Arrays, Strings, etc. Die Lebensdauer dieser Objekte ist nicht an eine Methode gebunden und wird vom Garbage Collector freigegeben, wenn sie nicht mehr benötigt werden.

Value Types

Value Types sind Typen bei denen die Variable direkt den Wert enthält, wie int, double, bool, etc.

int a = 1; int b = a; b = 2; // a ist immer noch 1, b ist 2

Reference Types

Reference Types sind Variablen die nur auf eine Adresse im Heap verweisen das ein Objekt enthält, wie class, string, int[], Person, etc.

Person p = new Person("John"); Person p2 = p; p2.Name = "Jane"; // p.Name ist jetzt "Jane", p2.Name ist auch "Jane"

Generisch

Generische Methode

Generische Methoden sind Methoden, die mit beliebigen Typen arbeiten können. Die Methoden kann verschiedene Typen annehmen.

public static T Identity<T>(T value) { return value; } int a = Identity(1); // T wird zu int string b = Identity("Hello"); // T wird zu string

Der Compiler generiert für jede benötigte Typen eine eigene Methode. Möchte man einen Typ erzwingen kann man den Typ mit angeben Identity<int>(1).

Was sind Generics(T)?

Generics sind Typen, die mit einem Platzhalter für einen bestimmten Typ arbeiten. Man verwendet sie um Code duplizierung zu vermeiden.

public class MyClass<T> { private T _value; public MyClass(T value) { _value = value; } } var myClass = new MyClass<int>(1); Console.WriteLine(myClass.Value);

Constraints

Muss man das T einschränken, weil man zwei Werte vergleichen will und der Compiler nicht weiß, ob der Typ vergleichbar ist, dann kann man das where Schlüsselwort verwenden.

// Nur Typen die IComparable<T> implementieren können verwendet werden public class MyClass<T> where T : IComparable<T> { public MyClass(T value) { _value = value; } }

Gängige Constraints sind:

  • where T : class - Typ muss eine Referenztyp sein
  • where T : struct - Typ muss ein Werttyp sein
  • where T : new() - Typ muss einen parameterlosen Konstruktor haben
  • where T : <BaseClass> - Typ muss von einer bestimmten Basisklasse erben
  • where T : <Interface> - Typ muss eine bestimmte Schnittstelle implementieren

Wofür default bei Generics

Mit default(T) kann man den Default Wert eines Typs erhalten. Abkürzen lässt es sich im Code mit T value = default;

Wenn der Typ einen Default Wert hat, dann wird dieser zurückgegeben.

Anonyme Typen

Eine Klasse ohne Namen ist ein anonymer Typ. Definiert werden sie mit var. Der Compiler generiert automatisch den Typ anhand der Eigenschaften. Null Werte sind nicht erlaubt.

var person = new { Name = "John", Age = 20 }; Console.WriteLine(person.Name); // Compiler generiert: internal class { public string Name { get; set; } public int Age { get; set; } }

Überladung von Operatoren

Operatoren wie +, -, usw. können überladen werden. Das heißt, dass man sie für bestimmte Typen neu definieren kann. Dadurch kann man z.B. zwei Objekte eines Typs addieren oder subtrahieren.

Um einen Operator zu überladen muss man folgende Regeln beachten:

public static ReturnType operator OperatorSymbol(type value1, ...)

Eine Operatorfunktion muss public und static sein.

public static MyClass operator +(MyClass a, MyClass b) { return new MyClass(a.Value + b.Value); }

Async, Await

Langwierige Operationen blockieren den Hauptthread und frieren dadruch die Benutzeroberfläche ein und der Benutzer kann nicht mehr interagieren.

Damit das Programm weiter auf Aktionen des Nutzers reagieren kann, wird nicht darauf gewartet, dass die Operation fertig ist, sondern die Operation wird in einem separaten Thread ausgeführt.

Um das im Code umzusetzen nutzt man async und await.

public async Task<int> LongRunningOperationAsync() { await Task.Delay(1000); return 42; } public async void ButtonClickAsync() { int result = await LongRunningOperationAsync(); Console.WriteLine(result); }
  • async - Signalisiert, dass die Methode asynchron ist.
  • await - Pausiert die Methode und wartet auf das Ergebnis der asynchronen Operation.
  • Task<int> - Der RückgabeTyp einer asynchronen Operation.
Last updated on