Omówienie dopasowywania wzorców
Dopasowywanie wzorca to technika, w której testujesz wyrażenie, aby określić, czy ma pewne cechy. Dopasowanie wzorca języka C# zapewnia bardziej zwięzłą składnię testowania wyrażeń i podejmowania akcji po dopasowaniu wyrażenia. Wyrażenie "is
" obsługuje dopasowywanie wzorca do testowania wyrażenia i warunkowo deklarowanie nowej zmiennej do wyniku tego wyrażenia. Wyrażenie "switch
umożliwia wykonywanie akcji na podstawie pierwszego pasującego wzorca dla wyrażenia. Te dwa wyrażenia obsługują bogate słownictwo wzorców.
Ten artykuł zawiera omówienie scenariuszy, w których można używać dopasowywania wzorców. Te techniki mogą poprawić czytelność i poprawność kodu. Aby zapoznać się z pełnym omówieniem wszystkich wzorców, które można zastosować, zobacz artykuł na temat wzorców w dokumentacji językowej.
Sprawdzanie wartości null
Jednym z najbardziej typowych scenariuszy dopasowywania wzorców jest zapewnienie, że wartości nie null
są . Podczas testowania null
można przetestować i przekonwertować typ wartości dopuszczalnej na wartość null przy użyciu następującego przykładu:
int? maybe = 12;
if (maybe is int number)
{
Console.WriteLine($"The nullable int 'maybe' has the value {number}");
}
else
{
Console.WriteLine("The nullable int 'maybe' doesn't hold a value");
}
Powyższy kod to wzorzec deklaracji do testowania typu zmiennej i przypisywania go do nowej zmiennej. Reguły językowe sprawiają, że ta technika jest bezpieczniejsza niż wiele innych. Zmienna number
jest dostępna tylko i przypisana w prawdziwej części klauzuli if
. Jeśli próbujesz uzyskać dostęp do niej w innym miejscu, w else
klauzuli if
lub po bloku, kompilator wystawia błąd. Po drugie, ponieważ nie używasz ==
operatora, ten wzorzec działa, gdy typ przeciąża ==
operatora. To sprawia, że jest to idealny sposób sprawdzania wartości referencyjnych o wartości null, dodając not
wzorzec:
string? message = "This is not the null string";
if (message is not null)
{
Console.WriteLine(message);
}
W poprzednim przykładzie użyto wzorca stałej , aby porównać zmienną z null
. Jest not
to wzorzec logiczny , który jest zgodny, gdy negowany wzorzec nie jest zgodny.
Testy typów
Innym typowym zastosowaniem dopasowania wzorca jest przetestowanie zmiennej w celu sprawdzenia, czy jest zgodna z danym typem. Na przykład poniższy kod sprawdza, czy zmienna ma wartość inną niż null i implementuje System.Collections.Generic.IList<T> interfejs. Jeśli tak, używa ICollection<T>.Count właściwości na tej liście, aby znaleźć indeks środkowy. Wzorzec deklaracji nie jest zgodny z wartością null
, niezależnie od typu czasu kompilacji zmiennej. Poniższy kod chroni przed null
elementem , oprócz ochrony przed typem, który nie implementuje IList
elementu .
public static T MidPoint<T>(IEnumerable<T> sequence)
{
if (sequence is IList<T> list)
{
return list[list.Count / 2];
}
else if (sequence is null)
{
throw new ArgumentNullException(nameof(sequence), "Sequence can't be null.");
}
else
{
int halfLength = sequence.Count() / 2 - 1;
if (halfLength < 0) halfLength = 0;
return sequence.Skip(halfLength).First();
}
}
Te same testy można zastosować w wyrażeniu switch
, aby przetestować zmienną pod kątem wielu różnych typów. Te informacje umożliwiają tworzenie lepszych algorytmów na podstawie określonego typu czasu wykonywania.
Porównanie wartości dyskretnych
Możesz również przetestować zmienną, aby znaleźć dopasowanie dla określonych wartości. Poniższy kod przedstawia jeden przykład, w którym testujesz wartość dla wszystkich możliwych wartości zadeklarowanych w wyliczeniem:
public State PerformOperation(Operation command) =>
command switch
{
Operation.SystemTest => RunDiagnostics(),
Operation.Start => StartSystem(),
Operation.Stop => StopSystem(),
Operation.Reset => ResetToReady(),
_ => throw new ArgumentException("Invalid enum value for command", nameof(command)),
};
W poprzednim przykładzie pokazano wysyłanie metody na podstawie wartości wyliczenia. Ostatnim _
przypadkiem jest wzorzec odrzucenia , który odpowiada wszystkim wartościom. Obsługuje wszelkie warunki błędu, w których wartość nie jest zgodna z jedną ze zdefiniowanych enum
wartości. Jeśli pominięto ramię przełącznika, kompilator ostrzega, że nie obsłużysz wszystkich możliwych wartości wejściowych. W czasie wykonywania wyrażenie zgłasza wyjątek, switch
jeśli badany obiekt nie pasuje do żadnego z ramion przełącznika. Można użyć stałych liczbowych zamiast zestawu wartości wyliczenia. Możesz również użyć tej podobnej techniki dla wartości ciągów stałych reprezentujących polecenia:
public State PerformOperation(string command) =>
command switch
{
"SystemTest" => RunDiagnostics(),
"Start" => StartSystem(),
"Stop" => StopSystem(),
"Reset" => ResetToReady(),
_ => throw new ArgumentException("Invalid string value for command", nameof(command)),
};
W poprzednim przykładzie pokazano ten sam algorytm, ale używa wartości ciągów zamiast wyliczenia. Ten scenariusz jest używany, jeśli aplikacja reaguje na polecenia tekstowe zamiast zwykłego formatu danych. Począwszy od języka C# 11, można również użyć elementu Span<char>
lub ReadOnlySpan<char>
, aby przetestować wartości ciągów stałych, jak pokazano w poniższym przykładzie:
public State PerformOperation(ReadOnlySpan<char> command) =>
command switch
{
"SystemTest" => RunDiagnostics(),
"Start" => StartSystem(),
"Stop" => StopSystem(),
"Reset" => ResetToReady(),
_ => throw new ArgumentException("Invalid string value for command", nameof(command)),
};
We wszystkich tych przykładach wzorzec odrzucania zapewnia obsługę wszystkich danych wejściowych. Kompilator pomaga, upewniając się, że każda możliwa wartość wejściowa jest obsługiwana.
Wzorce relacyjne
Możesz użyć wzorców relacyjnych , aby przetestować sposób porównywania wartości z stałymi. Na przykład poniższy kod zwraca stan wody na podstawie temperatury w Fahrenheit:
string WaterState(int tempInFahrenheit) =>
tempInFahrenheit switch
{
(> 32) and (< 212) => "liquid",
< 32 => "solid",
> 212 => "gas",
32 => "solid/liquid transition",
212 => "liquid / gas transition",
};
Powyższy kod pokazuje również wzorzec logiczny przyciętyand
, aby sprawdzić, czy oba wzorce relacyjne są zgodne. Możesz również użyć wzorca rozłącznego or
, aby sprawdzić, czy któryś wzorzec pasuje. Dwa wzorce relacyjne są otoczone nawiasami, których można używać wokół dowolnego wzorca w celu uzyskania jasności. Ostatnie dwie ramiona przełącznika obsługują przypadki topnienia i punktu wrzenia. Bez tych dwóch ramion kompilator ostrzega, że logika nie obejmuje wszystkich możliwych danych wejściowych.
Powyższy kod pokazuje również inną ważną funkcję, którą kompilator udostępnia dla wyrażeń pasujących do wzorca: kompilator ostrzega Cię, jeśli nie obsłużysz każdej wartości wejściowej. Kompilator wyświetla również ostrzeżenie, jeśli ramię przełącznika jest już obsługiwane przez poprzednie ramię przełącznika. Zapewnia to swobodę refaktoryzacji i zmieniania kolejności wyrażeń przełącznika. Innym sposobem na napisanie tego samego wyrażenia może być:
string WaterState2(int tempInFahrenheit) =>
tempInFahrenheit switch
{
< 32 => "solid",
32 => "solid/liquid transition",
< 212 => "liquid",
212 => "liquid / gas transition",
_ => "gas",
};
Kluczową lekcją w tej i innych refaktoryzacji lub zmiany kolejności jest to, że kompilator sprawdza, czy zostały omówione wszystkie dane wejściowe.
Wiele danych wejściowych
Wszystkie obserwowane do tej pory wzorce sprawdzały jedno dane wejściowe. Można pisać wzorce, które badają wiele właściwości obiektu. Rozważmy następujący Order
rekord:
public record Order(int Items, decimal Cost);
Poprzedni typ rekordu pozycyjnego deklaruje dwa elementy członkowskie na jawnych pozycjach. Pojawia się najpierw element Items
, a następnie kolejność Cost
. Aby uzyskać więcej informacji, zobacz Rekordy.
Poniższy kod analizuje liczbę elementów i wartość zamówienia w celu obliczenia obniżonej ceny:
public decimal CalculateDiscount(Order order) =>
order switch
{
{ Items: > 10, Cost: > 1000.00m } => 0.10m,
{ Items: > 5, Cost: > 500.00m } => 0.05m,
{ Cost: > 250.00m } => 0.02m,
null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
var someObject => 0m,
};
Pierwsze dwa ramiona badają dwie właściwości Order
obiektu . Trzeci sprawdza tylko koszt. Następne testy względem null
elementu i final są zgodne z dowolną inną wartością. Order
Jeśli typ definiuje odpowiednią Deconstruct
metodę, można pominąć nazwy właściwości ze wzorca i użyć dekonstrukcji do zbadania właściwości:
public decimal CalculateDiscount(Order order) =>
order switch
{
( > 10, > 1000.00m) => 0.10m,
( > 5, > 50.00m) => 0.05m,
{ Cost: > 250.00m } => 0.02m,
null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
var someObject => 0m,
};
Powyższy kod przedstawia wzorzec pozycyjny , w którym właściwości są zdekonstrukowane dla wyrażenia.
Wzorce listy
Elementy można sprawdzić na liście lub tablicy przy użyciu wzorca listy. Wzorzec listy zawiera metodę stosowania wzorca do dowolnego elementu sekwencji. Ponadto można zastosować wzorzec odrzucenia (_
), aby pasować do dowolnego elementu, lub zastosować wzorzec wycinka w celu dopasowania do zera lub większej liczby elementów.
Wzorce listy są cennym narzędziem, gdy dane nie są zgodne ze zwykłą strukturą. Możesz użyć dopasowania wzorca, aby przetestować kształt i wartości danych, zamiast przekształcać je w zestaw obiektów.
Rozważmy następujący fragment pliku tekstowego zawierającego transakcje bankowe:
04-01-2020, DEPOSIT, Initial deposit, 2250.00
04-15-2020, DEPOSIT, Refund, 125.65
04-18-2020, DEPOSIT, Paycheck, 825.65
04-22-2020, WITHDRAWAL, Debit, Groceries, 255.73
05-01-2020, WITHDRAWAL, #1102, Rent, apt, 2100.00
05-02-2020, INTEREST, 0.65
05-07-2020, WITHDRAWAL, Debit, Movies, 12.57
04-15-2020, FEE, 5.55
Jest to format CSV, ale niektóre wiersze mają więcej kolumn niż inne. Jeszcze gorzej w przypadku przetwarzania jedna kolumna w typie WITHDRAWAL
zawiera tekst generowany przez użytkownika i może zawierać przecinek w tekście. Wzorzec listy zawierający wzorzecodrzucania, wzorzec stałej i wzorzec var do przechwytywania danych przetwarzania wartości w tym formacie:
decimal balance = 0m;
foreach (string[] transaction in ReadRecords())
{
balance += transaction switch
{
[_, "DEPOSIT", _, var amount] => decimal.Parse(amount),
[_, "WITHDRAWAL", .., var amount] => -decimal.Parse(amount),
[_, "INTEREST", var amount] => decimal.Parse(amount),
[_, "FEE", var fee] => -decimal.Parse(fee),
_ => throw new InvalidOperationException($"Record {string.Join(", ", transaction)} is not in the expected format!"),
};
Console.WriteLine($"Record: {string.Join(", ", transaction)}, New balance: {balance:C}");
}
Powyższy przykład przyjmuje tablicę ciągów, gdzie każdy element jest jednym polem w wierszu. switch
Klucze wyrażeń w drugim polu, które określa rodzaj transakcji i liczbę pozostałych kolumn. Każdy wiersz zapewnia, że dane są w poprawnym formacie. Wzorzec odrzucenia (_
) pomija pierwsze pole z datą transakcji. Drugie pole odpowiada typowi transakcji. Pozostałe elementy pasują do pola z kwotą. Ostateczne dopasowanie używa wzorca var do przechwytywania reprezentacji ciągu kwoty. Wyrażenie oblicza kwotę do dodania lub odejmowania z salda.
Wzorce listy umożliwiają dopasowanie kształtu sekwencji elementów danych. Wzorce odrzucania i wycinka są używane do dopasowania do lokalizacji elementów. Inne wzorce służą do dopasowywania cech poszczególnych elementów.
Ten artykuł zawiera przewodnik po rodzajach kodu, który można napisać przy użyciu dopasowania wzorca w języku C#. W poniższych artykułach przedstawiono więcej przykładów użycia wzorców w scenariuszach oraz pełne słownictwo wzorców dostępnych do użycia.
Zobacz też
- Użyj dopasowania wzorca, aby uniknąć sprawdzania "is", po którym następuje rzutowanie (reguły stylu IDE0020 i IDE0038)
- Eksploracja: użyj dopasowania wzorca, aby utworzyć zachowanie klasy w celu uzyskania lepszego kodu
- Samouczek: używanie dopasowania wzorca do tworzenia algorytmów opartych na typach i opartych na danych
- Odwołanie: Dopasowywanie wzorca
Opinia
Prześlij i wyświetl opinię dla