22 Mai 2025
Das moderne C# hat eine stille Revolution in der Handhabung von Bedingungslogik erfahren. Vorbei sind die Zeiten, in denen Typüberprüfungen und Wertvergleiche ausufernde if-else
-Kaskaden oder ungeschickte switch
-Anweisungen erforderten. Die Einführung ausgeklügelter Mustervergleichsfunktionen, insbesondere seit C# 8.0, hat die Art und Weise, wie Entwickler den Kontrollfluss schreiben, grundlegend verändert – den Code gleichzeitig ausdrucksstärker, prägnanter und sicherer machend.
Mustervergleich in C# bietet eine prägnante Syntax, um einen Ausdruck zu testen und Aktionen auszuführen, wenn dieser Ausdruck einem bestimmten Muster entspricht. Er ermöglicht es Entwicklern, Werte anhand verschiedener Kriterien zu überprüfen, einschließlich ihres Typs, Werts, ihrer Eigenschaften oder sogar der Struktur komplexer Objekte. Diese Fähigkeit wird hauptsächlich durch den is
-Ausdruck und den switch
-Ausdruck (oder die switch
-Anweisung) verfügbar gemacht. Während grundlegende Typüberprüfungen bereits früher existierten, führte C# 8.0 ein reicheres Vokabular an Mustern ein, das seinen Nutzen und seine Auswirkungen auf die täglichen Programmierpraktiken erheblich erweiterte. Diese Verbesserung ist entscheidend für effektiven C# switch
-Mustervergleich und C# if
-Mustervergleich, indem sie ausufernde Bedingungslogik in kompakte, verständliche Konstrukte verwandelt.
Lassen Sie uns einige der verfügbaren Hauptmusterarten erkunden und wie sie gängige Programmieraufgaben vereinfachen, indem sie klare C# Mustervergleichsbeispiele liefern, die deren Vorteile veranschaulichen.
Die wahre Stärke des Mustervergleichs zeigt sich im Vergleich zu traditionellen Ansätzen. Entwickler erzielen erhebliche Verbesserungen in der Code-Lesbarkeit und Prägnanz.
Das Deklarationsmuster überprüft den Laufzeittyp eines Ausdrucks und weist das Ergebnis, wenn es übereinstimmt, einer neuen Variablen zu. Dies eliminiert die Notwendigkeit expliziter Typumwandlungen und null
-Prüfungen in vielen Szenarien.
Vorher:
public void ProcessAssetOld(object asset)
{
if (asset is SoftwareLicense)
{
SoftwareLicense license = (SoftwareLicense)asset;
Console.WriteLine($"Software License: {license.ProductName}, Expiration: {license.ExpirationDate.ToShortDateString()}");
}
else if (asset is HardwareComponent)
{
HardwareComponent hardware = (HardwareComponent)asset;
Console.WriteLine($"Hardware Component: {hardware.ComponentName}, Serial: {hardware.SerialNumber}");
}
else
{
Console.WriteLine("Unknown asset type.");
}
}
public class SoftwareLicense { public string ProductName { get; set; } public DateTime ExpirationDate { get; set; } }
public class HardwareComponent { public string ComponentName { get; set; } public string SerialNumber { get; set; } }
Nachher (unter Verwendung des Deklarationsmusters):
public void ProcessAssetNew(object asset)
{
if (asset is SoftwareLicense license)
{
Console.WriteLine($"Software License: {license.ProductName}, Expiration: {license.ExpirationDate.ToShortDateString()}");
}
else if (asset is HardwareComponent hardware)
{
Console.WriteLine($"Hardware Component: {hardware.ComponentName}, Serial: {hardware.SerialNumber}");
}
else
{
Console.WriteLine("Unknown asset type.");
}
}
Die Variablen license
und hardware
sind nur dann im Gültigkeitsbereich und zugewiesen, wenn das Muster übereinstimmt, was die Typsicherheit erhöht und potenzielle Laufzeitfehler reduziert.
Ähnlich dem Deklarationsmuster überprüft das Typmuster den Laufzeittyp eines Ausdrucks. Es wird oft innerhalb von switch
-Ausdrücken verwendet, wo eine neue Variablendeklaration für das Muster selbst nicht zwingend erforderlich ist.
Vorher:
public decimal CalculateShippingCostOld(Shipment shipment)
{
if (shipment == null)
throw new ArgumentNullException(nameof(shipment));
if (shipment.GetType() == typeof(StandardShipment))
return 5.00m;
if (shipment.GetType() == typeof(ExpressShipment))
return 15.00m;
if (shipment.GetType() == typeof(InternationalShipment))
return 25.00m;
return 0.00m; // Default for unknown types
}
public abstract class Shipment { }
public class StandardShipment : Shipment { }
public class ExpressShipment : Shipment { }
public class InternationalShipment : Shipment { }
Nachher (unter Verwendung des Typsmusters im switch
-Ausdruck):
public decimal CalculateShippingCostNew(Shipment shipment) => shipment switch
{
StandardShipment => 5.00m,
ExpressShipment => 15.00m,
InternationalShipment => 25.00m,
null => throw new ArgumentNullException(nameof(shipment)),
_ => 0.00m // Handle unknown shipment types
};
Dies demonstriert einen prägnanten C# switch
-Fall-Mustervergleich für verschiedene Typen.
Ein Konstantenmuster testet, ob das Ergebnis eines Ausdrucks einem angegebenen konstanten Wert entspricht. Dies vereinfacht diskrete Wertvergleiche.
Vorher:
public string GetOrderStateOld(OrderStatus status)
{
if (status == OrderStatus.Pending)
return "Order is awaiting processing.";
else if (status == OrderStatus.Shipped)
return "Order has been dispatched.";
else if (status == OrderStatus.Delivered)
return "Order has been delivered.";
else if (status == OrderStatus.Cancelled)
return "Order has been cancelled.";
else
return "Unknown order state.";
}
public enum OrderStatus { Pending, Shipped, Delivered, Cancelled, Returned }
Nachher (unter Verwendung des Konstantenmusters):
public string GetOrderStateNew(OrderStatus status) => status switch
{
OrderStatus.Pending => "Order is awaiting processing.",
OrderStatus.Shipped => "Order has been dispatched.",
OrderStatus.Delivered => "Order has been delivered.",
OrderStatus.Cancelled => "Order has been cancelled.",
_ => "Unknown order state."
};
Dies ist eine saubere Methode zur Handhabung spezifischer C# Zeichenketten-Mustervergleiche oder Enum-Werte.
Relationsmuster vergleichen das Ergebnis eines Ausdrucks mit einer Konstante unter Verwendung von Vergleichsoperatoren (<
, >
, <=
, >=
). Dies macht Bereichsprüfungen wesentlich lesbarer.
Vorher:
public string GetEmployeePerformanceLevelOld(int score)
{
if (score < 50)
return "Needs Improvement";
else if (score >= 50 && score < 70)
return "Meets Expectations";
else if (score >= 70 && score < 90)
return "Exceeds Expectations";
else if (score >= 90)
return "Outstanding";
else
return "Invalid Score"; // Should not happen with int, but for completeness
}
Nachher (unter Verwendung von Relationsmustern):
public string GetEmployeePerformanceLevelNew(int score) => score switch
{
< 50 => "Needs Improvement",
>= 50 and < 70 => "Meets Expectations",
>= 70 and < 90 => "Exceeds Expectations",
>= 90 => "Outstanding",
_ => "Invalid Score" // Catches any unexpected integer values
};
Dies zeigt die Stärke der Kombination von Relationsmustern mit logischen Mustern für den Mustervergleich in C#.
Das Eigenschaftenmuster ermöglicht den Vergleich der Eigenschaften oder Felder eines Ausdrucks mit verschachtelten Mustern. Dies ist unglaublich nützlich, um den Zustand von Objekten zu überprüfen.
Vorher:
public decimal CalculateOrderDiscountOld(CustomerOrder order)
{
if (order == null) return 0m;
if (order.TotalAmount >= 500 && order.Customer.IsPremium)
{
return 0.15m; // 15% for premium customers with large orders
}
else if (order.TotalAmount >= 100)
{
return 0.05m; // 5% for general large orders
}
return 0m;
}
public class Customer { public bool IsPremium { get; set; } }
public class CustomerOrder { public decimal TotalAmount { get; set; } public Customer Customer { get; set; } }
Nachher (unter Verwendung des Eigenschaftenmusters):
public decimal CalculateOrderDiscountNew(CustomerOrder order) => order switch
{
{ TotalAmount: >= 500, Customer.IsPremium: true } => 0.15m,
{ TotalAmount: >= 100 } => 0.05m,
null => 0m,
_ => 0m
};
Dies demonstriert den leistungsstarken C# Eigenschaftenmustervergleich, der Objektzustandsprüfungen vereinfacht.
Positionsmuster dekonstruieren das Ergebnis eines Ausdrucks (wie Tupel oder Records mit Deconstruct
-Methoden) und vergleichen die resultierenden Werte mit verschachtelten Mustern.
Vorher:
public string DescribeTransactionOld(Transaction transaction)
{
if (transaction.Type == TransactionType.Deposit && transaction.Amount > 1000)
return "Large Deposit";
else if (transaction.Type == TransactionType.Withdrawal && transaction.Amount > 500)
return "Significant Withdrawal";
else if (transaction.Type == TransactionType.Fee && transaction.Amount > 0)
return "Applied Fee";
else
return "Standard Transaction";
}
public record Transaction(TransactionType Type, decimal Amount);
public enum TransactionType { Deposit, Withdrawal, Fee, Transfer }
Nachher (unter Verwendung des Positionsmusters):
public string DescribeTransactionNew(Transaction transaction) => transaction switch
{
(TransactionType.Deposit, > 1000m) => "Large Deposit",
(TransactionType.Withdrawal, > 500m) => "Significant Withdrawal",
(TransactionType.Fee, > 0m) => "Applied Fee",
_ => "Standard Transaction"
};
Diese prägnante Form verbessert die Lesbarkeit des Mustervergleichs in C# für dekonstruierbare Typen.
and
, or
, not
)Logische Muster kombinieren andere Muster unter Verwendung der Schlüsselwörter and
, or
und not
, wodurch komplexe Bedingungsprüfungen innerhalb eines einzigen Musters ermöglicht werden.
Vorher:
public bool CanGrantAccessOld(UserRole role, int securityLevel, bool hasTwoFactorAuth)
{
if ((role == UserRole.Administrator || role == UserRole.Manager) && securityLevel > 7 && hasTwoFactorAuth)
return true;
else if (role == UserRole.Developer && securityLevel > 5)
return true;
else if (role != UserRole.Guest && securityLevel >= 3 && securityLevel <= 10)
return true;
else
return false;
}
public enum UserRole { Guest, User, Developer, Manager, Administrator }
Nachher (unter Verwendung logischer Muster):
public bool CanGrantAccessNew(UserRole role, int securityLevel, bool hasTwoFactorAuth) => (role, securityLevel, hasTwoFactorAuth) switch
{
(UserRole.Administrator or UserRole.Manager, > 7, true) => true,
(UserRole.Developer, > 5, _) => true,
(not UserRole.Guest, >= 3 and <= 10, _) => true,
_ => false
};
Der C# is
-Mustervergleich in Kombination mit logischen Operatoren wird sehr ausdrucksstark.
Das var
-Muster stimmt immer mit einem Ausdruck überein und weist dessen Ergebnis einer neuen lokalen Variablen zu. Es ist besonders nützlich, wenn Sie den gesamten Eingabewert eines switch
-Ausdrucks (oder einen Teil eines komplexen Musters) erfassen müssen, um zusätzliche, komplexere Prüfungen innerhalb einer when
-Klausel durchzuführen, insbesondere solche, die Methodenaufrufe oder Eigenschaften betreffen, die nicht direkt durch andere Muster ausgedrückt werden können.
Vorher:
using System.Collections.Generic;
using System.Linq;
public string AnalyzeNumberSequenceOld(IEnumerable<int> numbers)
{
if (numbers == null)
return "Invalid sequence (null)";
if (numbers.Count() == 0)
return "Empty sequence";
if (numbers.Count() > 5 && numbers.Average() > 10.0)
return "Long sequence with high average";
return "Normal sequence";
}
Nachher (unter Verwendung des Var-Musters mit when
-Klausel):
using System.Collections.Generic;
using System.Linq;
public string AnalyzeNumberSequenceNew(IEnumerable<int> numbers) => numbers switch
{
null => "Invalid sequence (null)",
var list when list.Count() == 0 => "Empty sequence",
var list when list.Count() > 5 && list.Average() > 10.0 => "Long sequence with high average",
_ => "Normal sequence"
};
Hier fängt var list
die Instanz von IEnumerable<int>
ab. Dies ermöglicht es, nachfolgende komplexe LINQ-Abfragen (wie Count()
und Average()
) direkt auf der Variablen list
innerhalb der when
-Klausel durchzuführen, ohne explizite Typüberprüfungen mittels is
zu benötigen.
In C# 11 eingeführt, ermöglichen Listenmuster den Vergleich mit Elementen in einer Sequenz (wie Arrays oder List<T>
). Sie sind unglaublich leistungsstark zur Validierung der Struktur und des Inhalts von Sammlungen.
Vorher:
public bool ValidateLogEntryOld(string[] logParts)
{
if (logParts == null || logParts.Length < 2) return false;
if (logParts[0] == "ERROR" && logParts.Length >= 3 && logParts[1] == "AUTH" && logParts[2] == "FAILED")
return true;
if (logParts[0] == "INFO" && logParts.Length >= 2 && logParts[1] == "CONNECTED")
return true;
if (logParts[0] == "WARN" && logParts.Length >= 4 && logParts[1] == "DISK" && logParts[2] == "SPACE" && int.TryParse(logParts[3], out int space) && space < 10)
return true;
return false;
}
Nachher (unter Verwendung von Listenmustern):
public bool ValidateLogEntryNew(string[] logParts) => logParts switch
{
["ERROR", "AUTH", "FAILED", ..] => true, // Error authentication failed
["INFO", "CONNECTED", ..] => true, // Client connected
["WARN", "DISK", "SPACE", var size, ..] when int.TryParse(size, out int space) && space < 10 => true, // Low disk space
_ => false // Any other log entry
};
Dies ist eine leistungsstarke Funktion für den Mustervergleich mit Sequenzen, besonders nützlich in Szenarien wie dem Parsen von Befehlszeilenargumenten oder Protokolleinträgen mit variierenden Strukturen. Das ..
(Slice-Muster) entspricht null oder mehr Elementen und bietet Flexibilität.
Die Vorteile des Mustervergleichs gehen über bloßen syntaktischen Zucker hinaus. Durch die Übernahme dieser Funktionen schreiben Entwickler von Natur aus Code, der:
if-else
-Blöcken und expliziten Typumwandlungen reduziert das Codevolumen, was zu weniger zu wartendem Code und weniger Fehlermöglichkeiten führt.switch
-Ausdrücke erfordern oft einen erschöpfenden Mustervergleich. Der C#-Compiler hilft, indem er Entwickler warnt, wenn ein switch
-Ausdruck nicht alle möglichen Eingabewerte abdeckt, was zur Vermeidung von Laufzeitausnahmen beiträgt. Diese vom Compiler erzwungene Vollständigkeit verbessert die Korrektheit der Software.Der Mustervergleich hat C# in eine Sprache verwandelt, die elegante Lösungen für komplexe Datenverarbeitung und den Kontrollfluss bietet. Von grundlegenden Typüberprüfungen bis hin zur Dekonstruktion von Objekten und der Validierung von Sequenzen sind die von C# 8.0+ bereitgestellten Funktionen unverzichtbare Werkzeuge für jeden modernen Softwareentwickler. Die Nutzung dieser leistungsstarken Funktionen verbessert nicht nur die Codequalität, sondern optimiert auch den Entwicklungsprozess und trägt zu zuverlässigeren und wartbareren Anwendungen bei. Entwickler, die sich immer noch auf ältere Stile von if-else
- oder switch
-Anweisungen verlassen, können erheblich davon profitieren, diese Muster zu integrieren, wodurch ihre C#-Programme robuster und angenehmer zu handhaben werden.