Werbung: Wenn Du ein gutes C# Lern-Buch benötigst, können wir Dir folgendes Buch von Amazon.de empfehlen:
11. Ereignisse
Ereignisse (Events) sind ein Mechanismus, um die Kommunikation zwischen Objekten zu ermöglichen. Sie basieren auf Delegates und sind besonders nützlich, wenn du Benachrichtigungen in einem Programm implementieren möchtest.
c# Beispiel für ein einfaches Ereignis:
// Delegate für das Ereignis
public delegate void AlarmHandler();
// Klasse, die das Ereignis auslöst
class Alarm
{
public event AlarmHandler AufAlarmAusgeloest;
public void Ausloesen()
{
if (AufAlarmAusgeloest != null)
{
AufAlarmAusgeloest();
}
}
}
// Klasse, die auf das Ereignis reagiert
class Polizei
{
public void ReagiereAufAlarm()
{
Console.WriteLine("Polizei: Wir reagieren auf den Alarm!");
}
}
// Nutzung des Ereignisses
Alarm alarm = new Alarm();
Polizei polizei = new Polizei();
// Ereignis abonnieren
alarm.AufAlarmAusgeloest += polizei.ReagiereAufAlarm;
// Ereignis auslösen
alarm.Ausloesen(); // Ausgabe: Polizei: Wir reagieren auf den Alarm!
In diesem Beispiel verwendet die Klasse Alarm
ein Ereignis namens AufAlarmAusgeloest
, und die Klasse Polizei
abonniert dieses Ereignis und reagiert darauf, wenn es ausgelöst wird.
Das waren jetzt einige fortgeschrittenere Konzepte in C#.
12. LINQ (Language Integrated Query)
LINQ (Language Integrated Query)
LINQ ist ein leistungsfähiges Feature in C#, das es dir ermöglicht, Datenabfragen direkt in C#-Code durchzuführen. Mit LINQ kannst du Daten aus verschiedenen Quellen wie Arrays, Listen oder Datenbanken abfragen, filtern, sortieren und transformieren, ähnlich wie SQL-Abfragen.
Kleiner Exkurs zu C# C-Sharp LINQ
LINQ (Language Integrated Query) ist eine Abfragesyntax in C#, die es Entwicklern ermöglicht, auf konsistente Weise auf Datenquellen zuzugreifen und diese zu manipulieren. Dabei kann es sich um verschiedene Datenquellen wie Arrays, Listen, XML, Datenbanken oder sogar Webdienste handeln. LINQ integriert die Abfragesyntax direkt in die C#-Sprache und macht es einfacher, Datenabfragen zu schreiben, die sowohl lesbar als auch effizient sind.
Grundlegende Vorteile von LINQ:
- Einheitliche Syntax: Mit LINQ kannst du dieselbe Abfragesyntax für unterschiedliche Datenquellen verwenden, wie z.B. SQL-Datenbanken, XML-Dateien, Listen oder Arrays.
- Typensicherheit: LINQ-Abfragen werden zur Compilezeit überprüft, was bedeutet, dass Fehler frühzeitig erkannt werden.
- Lesbarkeit: Die LINQ-Syntax ist oft einfacher zu lesen und zu schreiben als herkömmliche Schleifen oder komplexe manuelle Abfragen.
- Flexibilität: LINQ ermöglicht es dir, sowohl deklarative als auch imperative Programmierung zu kombinieren, was den Code sauberer und einfacher wartbar macht.
Zwei Hauptarten von LINQ-Syntaxen:
- Abfragesyntax (Query Syntax): Die Abfragesyntax von LINQ ähnelt SQL. Sie wird oft bevorzugt, wenn du eine SQL-ähnliche Struktur für deine Abfragen möchtest.Beispiel:
int[] zahlen = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var geradeZahlen = from zahl in zahlen
where zahl % 2 == 0
select zahl;
foreach (var zahl in geradeZahlen)
{
Console.WriteLine(zahl);
}
-
Erklärung: Hier wird das Array
zahlen
nach geraden Zahlen gefiltert, und die Ergebnisse werden ausgegeben. -
Methodensyntax (Method Syntax): Die Methodensyntax verwendet LINQ-Methodenaufrufe, wie
Where
,Select
,OrderBy
usw., und ist oft für komplexere Abfragen besser geeignet.Beispiel:
int[] zahlen = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var geradeZahlen = zahlen.Where(zahl => zahl % 2 == 0);
foreach (var zahl in geradeZahlen)
{
Console.WriteLine(zahl);
}
- Erklärung: Diese Syntax verwendet die
Where
-Methode, um das gleiche Ergebnis wie im ersten Beispiel zu erzielen. Sie ist oft kompakter und flexibel für das Verketteln von Methoden.
Wichtige LINQ-Methoden:
Where
: Filtert Elemente basierend auf einer Bedingung.Select
: Projiziert Elemente in eine neue Form.OrderBy
undOrderByDescending
: Sortiert Elemente in aufsteigender oder absteigender Reihenfolge.GroupBy
: Gruppiert Elemente basierend auf einem Schlüssel.Join
: Verknüpft zwei Sequenzen basierend auf einer Schlüsselbeziehung.First
,FirstOrDefault
,Single
,SingleOrDefault
: Gibt ein einzelnes Element zurück, basierend auf einer Bedingung.
Verwendung von LINQ mit Datenbanken:
Mit LINQ to SQL oder LINQ to Entities kannst du LINQ verwenden, um SQL-Datenbanken abzufragen. Dies ermöglicht es dir, SQL-Abfragen direkt in C# zu schreiben, ohne SQL selbst zu verwenden. Die Abfragen werden vom LINQ-Provider in SQL-Statements umgewandelt und an die Datenbank gesendet.
Ein C# Beispiel für eine C-Sharp LINQ-Abfrage auf einer Liste von Zahlen:
int[] zahlen = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// LINQ-Abfrage, um alle geraden Zahlen zu finden
var geradeZahlen = from zahl in zahlen
where zahl % 2 == 0
select zahl;
// Ausgabe der geraden Zahlen
foreach (var zahl in geradeZahlen)
{
Console.WriteLine(zahl);
}
Dieses Beispiel zeigt, wie du mit LINQ eine einfache Abfrage durchführen kannst, um nur die geraden Zahlen aus einer Liste zu filtern.
Es gibt auch die Methode-Syntax, die vielen Entwicklern vertrauter erscheint, da sie Ketten von Methodenaufrufen verwendet:
var geradeZahlen = zahlen.Where(zahl => zahl % 2 == 0);
foreach (var zahl in geradeZahlen)
{
Console.WriteLine(zahl);
}
LINQ kann auch mit komplexeren Datentypen verwendet werden, z. B. für Objekte:
class Person
{
public string Name { get; set; }
public int Alter { get; set; }
}
List<Person> personen = new List<Person>
{
new Person { Name = "Max", Alter = 25 },
new Person { Name = "Anna", Alter = 30 },
new Person { Name = "Tom", Alter = 22 }
};
// LINQ-Abfrage, um alle Personen über 24 zu finden
var ergebnis = from p in personen
where p.Alter > 24
select p;
foreach (var person in ergebnis)
{
Console.WriteLine(person.Name);
}
13. Asynchrone Programmierung mit async und await
Asynchrone Programmierung ist nützlich, um lang andauernde Aufgaben wie das Lesen von Dateien oder das Abrufen von Daten über das Netzwerk auszuführen, ohne das Hauptprogramm zu blockieren. In C# kannst du dies mit den Schlüsselwörtern async
und await
realisieren.
Beispiel für eine einfache asynchrone Methode:
// Asynchrone Methode, die eine Aufgabe simuliert
public async Task LangeOperationAsync()
{
Console.WriteLine("Lange Operation gestartet...");
await Task.Delay(3000); // Simulation einer Verzögerung von 3 Sekunden
Console.WriteLine("Lange Operation beendet.");
}
// Aufruf der Methode
await LangeOperationAsync();
Console.WriteLine("Programm läuft weiter...");
In diesem Beispiel pausiert die Methode LangeOperationAsync
, um die lang andauernde Aufgabe (in diesem Fall eine 3-sekündige Verzögerung) asynchron durchzuführen. Währenddessen kann das Hauptprogramm weiterlaufen, ohne blockiert zu werden.
Asynchrone Methoden geben oft ein Task
-Objekt zurück. Du kannst auch Task<T>
verwenden, wenn die Methode einen Wert zurückgeben soll:
public async Task<int> BerechneAsync()
{
await Task.Delay(2000); // 2 Sekunden warten
return 42;
}
int ergebnis = await BerechneAsync();
Console.WriteLine("Das Ergebnis ist: " + ergebnis);
14. Design Patterns in C#
Design Patterns sind bewährte Lösungen für häufig auftretende Probleme in der Softwareentwicklung.
Singleton Pattern
Das Singleton Pattern stellt sicher, dass eine Klasse nur eine Instanz hat und bietet einen globalen Zugriffspunkt darauf.
class Singleton
{
private static Singleton instance = null;
private static readonly object lockObj = new object();
private Singleton() { }
public static Singleton Instance
{
get
{
lock (lockObj)
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
public void ZeigeNachricht()
{
Console.WriteLine("Das ist ein Singleton.");
}
}
In diesem Beispiel sorgt das lock
-Objekt dafür, dass der Zugriff auf die Singleton-Instanz threadsicher ist. Nur eine Instanz der Singleton
-Klasse kann erstellt werden.
Factory Pattern
Das Factory Pattern wird verwendet, um die Erstellung von Objekten zu kapseln. Es ermöglicht es dir, Objekte zu erstellen, ohne den konkreten Klassennamen angeben zu müssen.
// Produkt-Interface
interface ITier
{
void MachGeräusch();
}
// Konkrete Produktklassen
class Hund : ITier
{
public void MachGeräusch()
{
Console.WriteLine("Wuff!");
}
}
class Katze : ITier
{
public void MachGeräusch()
{
Console.WriteLine("Miau!");
}
}
// Factory-Klasse
class TierFactory
{
public static ITier ErstelleTier(string tierTyp)
{
switch (tierTyp)
{
case "Hund":
return new Hund();
case "Katze":
return new Katze();
default:
throw new ArgumentException("Unbekanntes Tier");
}
}
}
// Nutzung der Factory
ITier tier = TierFactory.ErstelleTier("Hund");
tier.MachGeräusch(); // Ausgabe: Wuff!
Hier wird das Factory Pattern verwendet, um die Erstellung von Objekten der Klassen Hund
und Katze
zu kapseln.
Observer Pattern
Das Observer Pattern ermöglicht es einem Objekt (dem „Subject“), mehrere andere Objekte (die „Observers“) zu benachrichtigen, wenn sich sein Zustand ändert.
// Interface für den Observer
interface IObserver
{
void Update(string status);
}
// Konkreter Observer
class Beobachter : IObserver
{
public void Update(string status)
{
Console.WriteLine("Status aktualisiert: " + status);
}
}
// Subject-Klasse
class Subjekt
{
private List<IObserver> beobachterListe = new List<IObserver>();
private string status;
public void RegistriereBeobachter(IObserver beobachter)
{
beobachterListe.Add(beobachter);
}
public void SetzeStatus(string neuerStatus)
{
status = neuerStatus;
BenachrichtigeBeobachter();
}
private void BenachrichtigeBeobachter()
{
foreach (var beobachter in beobachterListe)
{
beobachter.Update(status);
}
}
}
// Nutzung des Observer Patterns
Subjekt subjekt = new Subjekt();
Beobachter beobachter = new Beobachter();
subjekt.RegistriereBeobachter(beobachter);
subjekt.SetzeStatus("Aktualisiert"); // Ausgabe: Status aktualisiert: Aktualisiert
In diesem Beispiel wird der Status des Subjekt
-Objekts aktualisiert, und alle registrierten Beobachter werden benachrichtigt.
15. Generics
Generics ermöglichen es dir, Klassen, Schnittstellen und Methoden zu schreiben, die unabhängig vom Datentyp funktionieren. Sie bieten mehr Flexibilität und ermöglichen dir, Code wiederzuverwenden, ohne auf bestimmte Typen festgelegt zu sein.
Ein einfaches Beispiel für eine generische Methode:
// Generische Methode, die zwei Werte vertauscht
public void Tausche<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
// Nutzung der generischen Methode
int x = 10, y = 20;
Tausche<int>(ref x, ref y);
Console.WriteLine("x: " + x + ", y: " + y); // Ausgabe: x: 20, y: 10
In diesem Beispiel wird die Methode Tausche
für verschiedene Datentypen verwendet, ohne dass sie mehrmals für jeden Typ geschrieben werden muss.
Generische Klassen ermöglichen es dir auch, Klassen zu erstellen, die mit beliebigen Typen arbeiten:
// Generische Klasse
class Box<T>
{
private T inhalt;
public void SetzeInhalt(T neuerInhalt)
{
inhalt = neuerInhalt;
}
public T HoleInhalt()
{
return inhalt;
}
}
// Nutzung der generischen Klasse
Box<int> intBox = new Box<int>();
intBox.SetzeInhalt(123);
Console.WriteLine("Inhalt der Box: " + intBox.HoleInhalt()); // Ausgabe: Inhalt der Box: 123
In diesem Beispiel kann die Klasse Box
für verschiedene Datentypen wie int
, string
usw. verwendet werden.
16. Erweiterungsmethoden
Erweiterungsmethoden ermöglichen es dir, bestehende Klassen um neue Methoden zu erweitern, ohne diese Klassen direkt zu verändern. Das ist nützlich, wenn du Funktionen hinzufügen möchtest, aber die ursprüngliche Klasse nicht ändern kannst.
So definierst du eine Erweiterungsmethode:
public static class Erweiterungen
{
// Erweiterungsmethode für die Klasse string
public static int WorteZählen(this string text)
{
return text.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
// Nutzung der Erweiterungsmethode
string satz = "Wie viele Worte sind in diesem Satz?";
int anzahlWoerter = satz.WorteZählen();
Console.WriteLine("Anzahl der Worte: " + anzahlWoerter); // Ausgabe: Anzahl der Worte: 7
In diesem Beispiel erweitern wir die Klasse string
um die Methode WorteZählen
, die die Anzahl der Wörter in einem Text zählt.
17. Nullable Types
C# bietet eine spezielle Möglichkeit, Werttypen (wie int
, bool
, double
usw.) als „nullable“ zu kennzeichnen, was bedeutet, dass sie zusätzlich zu ihrem Standardwert auch den Wert null
annehmen können. Das ist besonders nützlich, wenn du mit Datenbanken oder anderen externen Quellen arbeitest, wo Werte möglicherweise nicht vorhanden sind.
So machst du einen Werttyp nullable:
int? zahl = null;
if (zahl.HasValue)
{
Console.WriteLine("Zahl: " + zahl.Value);
}
else
{
Console.WriteLine("Keine Zahl vorhanden.");
}
Der Operator ?.
(Null-Conditional Operator) kann ebenfalls verwendet werden, um Methoden nur aufzurufen, wenn der Wert nicht null
ist:
string text = null;
Console.WriteLine(text?.Length); // Ausgabe: (nichts, da text null ist)
18. Reflection
Reflection ermöglicht es, Informationen über Typen zur Laufzeit zu erhalten und sogar Instanzen von Typen zu erstellen oder auf Methoden und Felder zuzugreifen, ohne diese direkt zu kennen. Dies ist nützlich, wenn du zur Laufzeit dynamisch auf Klassen zugreifen möchtest.
Ein einfaches Beispiel:
using System;
using System.Reflection;
class Beispiel
{
public void Methode()
{
Console.WriteLine("Methode wurde aufgerufen.");
}
}
// Nutzung von Reflection
Type typ = typeof(Beispiel);
MethodInfo methodeInfo = typ.GetMethod("Methode");
// Instanz der Klasse erstellen und Methode aufrufen
object instanz = Activator.CreateInstance(typ);
methodeInfo.Invoke(instanz, null); // Ausgabe: Methode wurde aufgerufen.
Reflection ermöglicht es dir also, dynamisch auf Typen und deren Mitglieder zuzugreifen, was besonders nützlich in Frameworks oder Tools ist, die dynamische Funktionalitäten bieten müssen.
19. Ausdrucksbäume (Expression Trees)
Ausdrucksbäume sind eine Möglichkeit, Code als Daten darzustellen, sodass du den Code analysieren, umschreiben oder sogar dynamisch zur Laufzeit ausführen kannst. Ausdrucksbäume sind eine erweiterte Technik, die häufig in Bibliotheken wie LINQ verwendet wird.
Beispiel:
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
// Ausdrucksbaum für eine einfache mathematische Operation
Expression<Func<int, int, int>> expr = (a, b) => a + b;
// Analysiere den Ausdruck
Console.WriteLine("Ausdruck: " + expr);
// Kompiliere und führe den Ausdruck aus
var func = expr.Compile();
int ergebnis = func(2, 3);
Console.WriteLine("Ergebnis: " + ergebnis); // Ausgabe: Ergebnis: 5
}
}
In diesem Beispiel haben wir einen Ausdrucksbaum erstellt, der eine einfache Addition repräsentiert. Der Ausdruck kann analysiert, kompiliert und ausgeführt werden.
20. Dependency Injection (DI)
Dependency Injection ist ein Designprinzip, bei dem Abhängigkeiten einer Klasse von außen bereitgestellt werden, anstatt dass die Klasse selbst entscheidet, welche Abhängigkeiten sie benötigt. Das führt zu einem flexibleren und testbareren Code.
In C# wird DI häufig über Konstruktor-Injektion durchgeführt:
// Schnittstelle
public interface IDienst
{
void Ausfuehren();
}
// Implementierung
public class Dienst : IDienst
{
public void Ausfuehren()
{
Console.WriteLine("Dienst wird ausgeführt.");
}
}
// Klasse, die den Dienst nutzt
public class Verbraucher
{
private readonly IDienst _dienst;
public Verbraucher(IDienst dienst)
{
_dienst = dienst;
}
public void Starte()
{
_dienst.Ausfuehren();
}
}
// Nutzung von Dependency Injection
IDienst dienst = new Dienst();
Verbraucher verbraucher = new Verbraucher(dienst);
verbraucher.Starte(); // Ausgabe: Dienst wird ausgeführt.
Hier injizieren wir den Dienst in die Verbraucher
-Klasse, sodass sie nicht fest an eine konkrete Implementierung gebunden ist. Dies erleichtert auch das Testen, da du während eines Tests einen Mock-Dienst bereitstellen kannst.