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 2Reference 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 stringDer 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 seinwhere T : struct- Typ muss ein Werttyp seinwhere T : new()- Typ muss einen parameterlosen Konstruktor habenwhere T : <BaseClass>- Typ muss von einer bestimmten Basisklasse erbenwhere 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.