Udostępnij za pośrednictwem


Samouczek: używanie dopasowywania wzorców do tworzenia algorytmów opartych na typach i opartych na danych

Możesz napisać funkcjonalność, która zachowuje się tak, jakbyś rozszerzał typy, które mogą pochodzić z innych bibliotek. Innym zastosowaniem wzorców jest utworzenie funkcji, której aplikacja wymaga, aby nie była to podstawowa funkcja typu, która jest rozszerzana.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Rozpoznawanie sytuacji, w których należy używać dopasowywania wzorców.
  • Użyj wyrażeń dopasowywania wzorców, aby zaimplementować zachowanie na podstawie typów i wartości właściwości.
  • Połącz dopasowywanie wzorca z innymi technikami, aby utworzyć kompletne algorytmy.

Wymagania wstępne

  • Najnowszy .NET SDK
  • Edytor programu Visual Studio Code
  • Zestaw deweloperski C#

Instrukcje instalacji

W systemie Windows użyj tego pliku konfiguracyjnego WinGet , aby zainstalować wszystkie wymagane komponenty wstępne. Jeśli masz już coś zainstalowanego, usługa WinGet pominie ten krok.

  1. Pobierz plik i kliknij dwukrotnie, aby go uruchomić.
  2. Przeczytaj umowę licencyjną, wpisz yi wybierz pozycję Wprowadź po wyświetleniu monitu o zaakceptowanie.
  3. Jeśli na pasku zadań zostanie wyświetlony monit kontroli konta użytkownika (UAC), zezwól na kontynuowanie instalacji.

Na innych platformach należy zainstalować każdy z tych składników oddzielnie.

  1. Pobierz zalecany instalator ze strony pobierania zestawu SDK platformy .NET i kliknij dwukrotnie, aby go uruchomić. Strona pobierania wykrywa platformę i zaleca najnowszy instalator twojej platformy.
  2. Pobierz najnowszy instalator z strony głównej programu Visual Studio Code i kliknij dwukrotnie, aby go uruchomić. Ta strona wykrywa również platformę, a link powinien być poprawny dla twojego systemu.
  3. Kliknij przycisk "Zainstaluj" na stronie rozszerzenia C# DevKit. Spowoduje to otwarcie programu Visual Studio Code i pytanie, czy chcesz zainstalować lub włączyć rozszerzenie. Wybierz pozycję "Zainstaluj".

W tym samouczku założono, że znasz języki C# i .NET, w tym program Visual Studio lub interfejs wiersza polecenia platformy .NET.

Scenariusze dopasowywania wzorców

Nowoczesne programowanie często obejmuje integrowanie danych z wielu źródeł oraz prezentowanie informacji i szczegółowych informacji z tych danych w jednej aplikacji spójnych. Ty i Twój zespół nie będą mieć kontroli ani dostępu do wszystkich typów reprezentujących dane przychodzące.

Klasyczny projekt zorientowany na obiekt wywołuje tworzenie typów w aplikacji reprezentujących każdy typ danych z tych wielu źródeł danych. Następnie aplikacja będzie pracować z tymi nowymi typami, tworzyć hierarchie dziedziczenia, tworzyć metody wirtualne i implementować abstrakcji. Te techniki działają, a czasami są to najlepsze narzędzia. Innym razem możesz napisać mniej kodu. Możesz napisać bardziej przejrzysty kod przy użyciu technik, które oddzielają dane od operacji, które manipulują danymi.

W tym samouczku utworzysz i zapoznasz się z aplikacją, która pobiera dane przychodzące z kilku źródeł zewnętrznych w jednym scenariuszu. Zobaczysz, jak dopasowanie wzorca zapewnia wydajny sposób korzystania z tych danych i ich przetwarzania w sposób, który nie był częścią oryginalnego systemu.

Rozważmy duży obszar metropolitalny stosujący opłaty i ceny za przejazd w godzinach szczytu do zarządzania ruchem. Piszesz aplikację, która oblicza opłaty za pojazd na podstawie jego typu. Późniejsze ulepszenia obejmują ceny oparte na liczbie pasażerów w pojeździe. Dalsze usprawnienia wprowadzają ceny w zależności od godziny i dnia tygodnia.

Z tego krótkiego opisu można szybko naszkicować hierarchię obiektów w celu modelowania tego systemu. Dane pochodzą jednak z wielu źródeł, takich jak inne systemy zarządzania rejestracją pojazdów. Te systemy udostępniają różne klasy do modelowania tych danych i nie masz jednego modelu obiektów, którego można użyć. W tym samouczku użyjesz tych uproszczonych klas do stworzenia modelu danych pojazdów z tych systemów zewnętrznych, co pokazano w poniższym kodzie:

namespace ConsumerVehicleRegistration
{
    public class Car
    {
        public int Passengers { get; set; }
    }
}

namespace CommercialRegistration
{
    public class DeliveryTruck
    {
        public int GrossWeightClass { get; set; }
    }
}

namespace LiveryRegistration
{
    public class Taxi
    {
        public int Fares { get; set; }
    }

    public class Bus
    {
        public int Capacity { get; set; }
        public int Riders { get; set; }
    }
}

Kod początkowy można pobrać z repozytorium GitHub dotnet/samples . Widać, że klasy pojazdów pochodzą z różnych systemów i znajdują się w różnych przestrzeniach nazw. Nie można używać żadnej wspólnej klasy bazowej poza System.Object.

Dopasowywanie wzorów

Scenariusz używany w tym samouczku pokazuje rodzaje problemów, do rozwiązywania których dobrze nadaje się dopasowywanie wzorców.

  • Obiekty, z którymi musisz pracować, nie są w hierarchii obiektów zgodnej z celami. Możesz pracować z klasami, które są częścią niepowiązanych systemów.
  • Dodawane funkcje nie są częścią podstawowej abstrakcji dla tych klas. Opłaty płatne przez pojazd zmieniają się w przypadku różnych typów pojazdów, ale opłaty nie są podstawową funkcją pojazdu.

Gdy kształt danych i operacje na tych danych nie są opisane razem, funkcje dopasowywania wzorców w języku C# ułatwiają pracę.

Implementowanie podstawowych obliczeń opłat drogowych

Najbardziej podstawowe obliczenie opłat opiera się tylko na typie pojazdu:

  • Cena Car to $2.00.
  • Kosztuje Taxi 3,50 dolara.
  • A Bus kosztuje 5,00 dolarów.
  • DeliveryTruck kosztuje 10,00 USD

Utwórz nową TollCalculator klasę i zaimplementuj dopasowywanie wzorca dla typu pojazdu, aby uzyskać kwotę opłat. Poniższy kod przedstawia początkową implementację obiektu TollCalculator.

using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

namespace Calculators;

public class TollCalculator
{
    public decimal CalculateToll(object vehicle) =>
        vehicle switch
    {
        Car c           => 2.00m,
        Taxi t          => 3.50m,
        Bus b           => 5.00m,
        DeliveryTruck t => 10.00m,
        { }             => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
        null            => throw new ArgumentNullException(nameof(vehicle))
    };
}

Powyższy kod używa switch wyrażenia (różniącego się od switch instrukcji), które testuje schemat deklaracji. Wyrażenie przełącznika zaczyna się od zmiennej vehicle w poprzednim kodzie, po której następuje słowo kluczowe switch. Następnie wszystkie ramiona przełącznika znajdują się wewnątrz nawiasów klamrowych. Wyrażenie switch dokonuje dalszych uściśleń składni otaczającej instrukcję switch. Słowo case kluczowe zostanie pominięte, a wynikiem każdego ramienia jest wyrażenie. Ostatnie dwie gałęzie pokazują nową cechę językową. Wariant { } pasuje do dowolnego obiektu innego niż null, który nie pasuje do wcześniejszej gałęzi. Mechanizm ten wyłapuje wszelkie nieprawidłowe typy przekazane do tej metody. Przypadek { } musi być zgodny z przypadkami dla każdego typu pojazdu. Gdyby kolejność została odwrócona, { } sprawa miałaby pierwszeństwo. Na koniec wzorzec nullstałej wykrywa, kiedy null jest przekazywany do tej metody. Wzorzec null może być ostatni, ponieważ inne wzorce pasują tylko do obiektu innego niż null poprawnego typu.

Ten kod można przetestować przy użyciu następującego kodu w pliku Program.cs:

using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

using toll_calculator;

var tollCalc = new TollCalculator();

var car = new Car();
var taxi = new Taxi();
var bus = new Bus();
var truck = new DeliveryTruck();

Console.WriteLine($"The toll for a car is {tollCalc.CalculateToll(car)}");
Console.WriteLine($"The toll for a taxi is {tollCalc.CalculateToll(taxi)}");
Console.WriteLine($"The toll for a bus is {tollCalc.CalculateToll(bus)}");
Console.WriteLine($"The toll for a truck is {tollCalc.CalculateToll(truck)}");

try
{
    tollCalc.CalculateToll("this will fail");
}
catch (ArgumentException e)
{
    Console.WriteLine("Caught an argument exception when using the wrong type");
}
try
{
    tollCalc.CalculateToll(null!);
}
catch (ArgumentNullException e)
{
    Console.WriteLine("Caught an argument exception when using null");
}

Ten kod jest zawarty w projekcie startowym, ale jest komentowany. Usuń komentarze i możesz sprawdzić, co zostało napisane.

Zaczynasz widzieć, jak wzorce mogą pomóc w tworzeniu algorytmów, w których kod i dane są oddzielone. Wyrażenie switch testuje typ i generuje różne wartości na podstawie wyników. To dopiero początek.

Dodaj cennik zajętości

Zarząd dróg płatnych chce zachęcić pojazdy do podróżowania z maksymalnym obciążeniem. Zdecydowali się pobierać więcej opłat, gdy pojazdy mają mniej pasażerów i zachęcają pełne pojazdy, oferując niższe ceny:

  • Samochody i taksówki bez pasażerów płacą dodatkowe 0,50 dolarów.
  • Samochody i taksówki z dwoma pasażerami otrzymują zniżkę w wysokości 0,50 USD.
  • Samochody i taksówki z trzema lub więcej pasażerami otrzymują zniżkę w wysokości 1,00 USD.
  • Autobusy, które są mniej niż 50% pełne płacą dodatkowe 2,00 dolarów.
  • Autobusy, które są wypełnione w ponad 90%, otrzymują zniżkę w wysokości 1 dolara.

Te reguły można zaimplementować przy użyciu wzorca właściwości w tym samym wyrażeniu przełącznika. Wzorzec właściwości porównuje wartość właściwości z wartością stałą. Wzorzec właściwości sprawdza właściwości obiektu po ustaleniu typu. Pojedynczy przypadek Car rozszerza się do czterech różnych przypadków.

vehicle switch
{
    Car {Passengers: 0} => 2.00m + 0.50m,
    Car {Passengers: 1} => 2.0m,
    Car {Passengers: 2} => 2.0m - 0.50m,
    Car                 => 2.00m - 1.0m,

    // ...
};

Pierwsze trzy przypadki przetestuj typ jako Car, a następnie sprawdź wartość Passengers właściwości. Jeśli oba są zgodne, to wyrażenie jest obliczane i zwracane.

Można również rozszerzyć przypadki taksówek w podobny sposób:

vehicle switch
{
    // ...

    Taxi {Fares: 0}  => 3.50m + 1.00m,
    Taxi {Fares: 1}  => 3.50m,
    Taxi {Fares: 2}  => 3.50m - 0.50m,
    Taxi             => 3.50m - 1.00m,

    // ...
};

Następnie zaimplementuj reguły zajętości, rozszerzając przypadki dla autobusów, jak pokazano w poniższym przykładzie:

vehicle switch
{
    // ...

    Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
    Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
    Bus => 5.00m,

    // ...
};

Urząd opłat nie jest zaniepokojony liczbą pasażerów w ciężarówkach dostawczych. Zamiast tego dostosowują kwotę opłat w oparciu o klasę wagową ciężarówek w następujący sposób:

  • Dla ciężarówek o masie przekraczającej 5000 funtów naliczana jest dodatkowa opłata w wysokości 5,00 USD.
  • Lekkie ciężarówki poniżej 3000 funtów otrzymują rabat w wysokości 2,00 USD.

Ta reguła jest implementowana przy użyciu następującego kodu:

vehicle switch
{
    // ...

    DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
    DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
    DeliveryTruck => 10.00m,
};

Poprzedni kod przedstawia klauzulę when gałęzi przełącznika. Używaj klauzuli when, aby testować warunki inne niż równość względem właściwości. Po zakończeniu będziesz mieć metodę podobną do następującego kodu:

vehicle switch
{
    Car {Passengers: 0}        => 2.00m + 0.50m,
    Car {Passengers: 1}        => 2.0m,
    Car {Passengers: 2}        => 2.0m - 0.50m,
    Car                        => 2.00m - 1.0m,

    Taxi {Fares: 0}  => 3.50m + 1.00m,
    Taxi {Fares: 1}  => 3.50m,
    Taxi {Fares: 2}  => 3.50m - 0.50m,
    Taxi             => 3.50m - 1.00m,

    Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
    Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
    Bus => 5.00m,

    DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
    DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
    DeliveryTruck => 10.00m,

    { }     => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
    null    => throw new ArgumentNullException(nameof(vehicle))
};

Wiele z tych ramion przełącznika to przykłady rekursywnych wzorców. Na przykład Car { Passengers: 1} pokazuje stały wzorzec wewnątrz wzorca właściwości.

Ten kod może być mniej powtarzalny przy użyciu zagnieżdżonych przełączników. Zarówno Car, jak i Taxi mają cztery różne ramiona w poprzednich przykładach. W obu przypadkach można utworzyć wzorzec deklaracji, który wchodzi w skład wzorca stałej. Ta technika jest pokazana w następującym kodzie:

public decimal CalculateToll(object vehicle) =>
    vehicle switch
    {
        Car c => c.Passengers switch
        {
            0 => 2.00m + 0.5m,
            1 => 2.0m,
            2 => 2.0m - 0.5m,
            _ => 2.00m - 1.0m
        },

        Taxi t => t.Fares switch
        {
            0 => 3.50m + 1.00m,
            1 => 3.50m,
            2 => 3.50m - 0.50m,
            _ => 3.50m - 1.00m
        },

        Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
        Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
        Bus b => 5.00m,

        DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
        DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
        DeliveryTruck t => 10.00m,

        { }  => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
        null => throw new ArgumentNullException(nameof(vehicle))
    };

W poprzednim przykładzie użycie wyrażenia rekursywnego oznacza, że nie powtarzasz ramienia Car i ramienia Taxi zawierających podrzędne ramiona, które testują wartość właściwości. Ta technika nie jest używana dla ramion Bus i DeliveryTruck, ponieważ te ramiona testują zakresy dla właściwości, a nie dyskretne wartości.

Dodawanie szczytowych cen

Dla ostatniej funkcji zarząd dróg chce dodać ceny szczytowe zależne od pory. W godzinach porannych i wieczornych szczytu opłaty są podwoine. Ta zasada wpływa tylko na ruch w jednym kierunku: przychodzący do miasta rano i wychodzący w godzinach szczytu wieczorem. W innych okresach w ciągu dnia roboczego opłaty wzrastają o 50%. Późną nocą i wczesnym rankiem opłaty są zmniejszane o 25%. W weekend jest to normalna stawka, niezależnie od czasu. Możesz użyć serii instrukcji if i else , aby wyrazić to przy użyciu następującego kodu:

public decimal PeakTimePremiumIfElse(DateTime timeOfToll, bool inbound)
{
    if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
        (timeOfToll.DayOfWeek == DayOfWeek.Sunday))
    {
        return 1.0m;
    }
    else
    {
        int hour = timeOfToll.Hour;
        if (hour < 6)
        {
            return 0.75m;
        }
        else if (hour < 10)
        {
            if (inbound)
            {
                return 2.0m;
            }
            else
            {
                return 1.0m;
            }
        }
        else if (hour < 16)
        {
            return 1.5m;
        }
        else if (hour < 20)
        {
            if (inbound)
            {
                return 1.0m;
            }
            else
            {
                return 2.0m;
            }
        }
        else // Overnight
        {
            return 0.75m;
        }
    }
}

Powyższy kod działa poprawnie, ale nie jest czytelny. Musisz przeanalizować wszystkie przypadki wejściowe i zagnieżdżone instrukcje if, aby zrozumieć logikę kodu. Zamiast tego użyjesz dopasowywania wzorców dla tej cechy, ale zintegrujesz je z innymi technikami. Można utworzyć jedno wyrażenie dopasowania wzorca, które będzie uwzględniać wszystkie kombinacje kierunku, dnia tygodnia i godziny. Wynik byłby skomplikowanym wyrażeniem. Trudno by było czytać i trudno zrozumieć. To sprawia, że trudno jest zapewnić poprawność. Zamiast tego połącz te metody, aby utworzyć krotkę wartości, która zwięzłie opisuje wszystkie te stany. Następnie użyj dopasowania wzorców, aby obliczyć mnożnik opłaty. Krotka zawiera trzy odrębne warunki:

  • Dzień to dzień tygodnia lub weekend.
  • Przedział czasu, kiedy pobierana jest opłata.
  • Kierunek to do miasta lub z miasta

W poniższej tabeli przedstawiono kombinacje wartości wejściowych i mnożnik cen szczytowych:

Dzień Czas Kierunek Wysokiej klasy
Dzień roboczy poranny pośpiech Przychodzące x 2.00
Dzień roboczy poranny pośpiech wychodzący x 1.00
Dzień roboczy pora dzienna Przychodzące x 1.50
Dzień roboczy pora dzienna wychodzący x 1.50
Dzień roboczy wieczorny pośpiech Przychodzące x 1.00
Dzień roboczy wieczorny pośpiech wychodzący x 2.00
Dzień roboczy Przez noc Przychodzące x 0,75
Dzień roboczy Przez noc wychodzący x 0,75
Koniec tygodnia poranny pośpiech Przychodzące x 1.00
Koniec tygodnia poranny pośpiech wychodzący x 1.00
Koniec tygodnia pora dzienna Przychodzące x 1.00
Koniec tygodnia pora dzienna wychodzący x 1.00
Koniec tygodnia wieczorny pośpiech Przychodzące x 1.00
Koniec tygodnia wieczorny pośpiech wychodzący x 1.00
Koniec tygodnia Przez noc Przychodzące x 1.00
Koniec tygodnia Przez noc wychodzący x 1.00

Istnieją 16 różnych kombinacji trzech zmiennych. Łącząc niektóre warunki, uprościsz ostateczne wyrażenie przełącznika.

System, który zbiera opłaty, używa struktury DateTime na czas, w którym opłata została pobrana. Tworzenie metod składowych, które tworzą zmienne z poprzedniej tabeli. Poniższa funkcja używa wyrażenia switch do dopasowania wzorca, aby wyrazić, czy DateTime reprezentuje weekend, czy dzień powszedni.

private static bool IsWeekDay(DateTime timeOfToll) =>
    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Monday    => true,
        DayOfWeek.Tuesday   => true,
        DayOfWeek.Wednesday => true,
        DayOfWeek.Thursday  => true,
        DayOfWeek.Friday    => true,
        DayOfWeek.Saturday  => false,
        DayOfWeek.Sunday    => false
    };

Ta metoda jest poprawna, ale jest powtórzona. Można ją uprościć, jak pokazano w poniższym kodzie:

private static bool IsWeekDay(DateTime timeOfToll) =>
    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Saturday => false,
        DayOfWeek.Sunday => false,
        _ => true
    };

Następnie dodaj podobną funkcję, aby podzielić czas na bloki:

private enum TimeBand
{
    MorningRush,
    Daytime,
    EveningRush,
    Overnight
}

private static TimeBand GetTimeBand(DateTime timeOfToll) =>
    timeOfToll.Hour switch
    {
        < 6 or > 19 => TimeBand.Overnight,
        < 10 => TimeBand.MorningRush,
        < 16 => TimeBand.Daytime,
        _ => TimeBand.EveningRush,
    };

Dodasz prywatny element enum , aby przekonwertować każdy zakres czasu na wartość dyskretną. GetTimeBand Następnie metoda używa wzorców relacyjnych i or sprzężenia. Wzorzec relacyjny umożliwia testowanie wartości liczbowej przy użyciu wartości <, , ><=lub >=. Wzorzec testuje, czy wyrażenie pasuje do jednego lub kilku wzorców. Można również użyć and wzorca, aby upewnić się, że wyrażenie pasuje do dwóch odrębnych wzorców, a not wzorzec do testowania, że wyrażenie nie pasuje do wzorca.

Po utworzeniu tych metod, można użyć innego wyrażenia switch ze wzorcem krotki, aby obliczyć cenę premium. Możesz zbudować wyrażenie switch ze wszystkimi 16 ramionami.

public decimal PeakTimePremiumFull(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true) => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime, true) => 1.50m,
        (true, TimeBand.Daytime, false) => 1.50m,
        (true, TimeBand.EveningRush, true) => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight, true) => 0.75m,
        (true, TimeBand.Overnight, false) => 0.75m,
        (false, TimeBand.MorningRush, true) => 1.00m,
        (false, TimeBand.MorningRush, false) => 1.00m,
        (false, TimeBand.Daytime, true) => 1.00m,
        (false, TimeBand.Daytime, false) => 1.00m,
        (false, TimeBand.EveningRush, true) => 1.00m,
        (false, TimeBand.EveningRush, false) => 1.00m,
        (false, TimeBand.Overnight, true) => 1.00m,
        (false, TimeBand.Overnight, false) => 1.00m,
    };

Powyższy kod działa, ale można go uprościć. Wszystkie osiem kombinacji na weekend ma tę samą opłatę. Możesz zastąpić wszystkie osiem następującym wierszem:

(false, _, _) => 1.0m,

Zarówno ruch przychodzący, jak i wychodzący mają ten sam mnożnik w ciągu dnia tygodnia i w godzinach nocnych. Te cztery ramiona przełącznika można zastąpić następującymi dwoma liniami:

(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _)   => 1.5m,

Kod powinien wyglądać podobnie do następującego kodu po tych dwóch zmianach:

public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true)  => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime,     _)     => 1.50m,
        (true, TimeBand.EveningRush, true)  => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight,   _)     => 0.75m,
        (false, _,                   _)     => 1.00m,
    };

Na koniec możesz usunąć dwie godziny szczytu, w ramach których obowiązuje zwykła cena. Po usunięciu tych ramion przełącznika, można zastąpić false odrzuceniem (_) w ostatnim ramieniu przełącznika. Będziesz mieć następującą gotową metodę:

public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.Overnight, _) => 0.75m,
        (true, TimeBand.Daytime, _) => 1.5m,
        (true, TimeBand.MorningRush, true) => 2.0m,
        (true, TimeBand.EveningRush, false) => 2.0m,
        _ => 1.0m,
    };

W tym przykładzie wyróżniono jedną z zalet dopasowywania wzorca: gałęzie wzorca są oceniane w kolejności. Jeśli przestawisz je tak, aby wcześniejsza gałąź kodu obsługiwała jeden z Twoich późniejszych przypadków, kompilator ostrzeże Cię o nieosiągalnym kodzie. Te reguły językowe ułatwiły wykonywanie powyższych ułatwień z ufnością, że kod nie uległ zmianie.

Dopasowywanie wzorca sprawia, że niektóre typy kodu są bardziej czytelne i oferują alternatywę dla technik zorientowanych na obiekty, gdy nie można dodać kodu do klas. Chmura powoduje, że dane i funkcje działają oddzielnie. Kształt danych i operacji na nim niekoniecznie są opisane razem. W tym samouczku wykorzystaliśmy istniejące dane w zupełnie inny sposób niż ich oryginalna funkcja. Dopasowanie wzorca umożliwia pisanie funkcji, które zastępują te typy, mimo że nie można ich rozszerzyć.

Następne kroki

Gotowy kod można pobrać z repozytorium GitHub dotnet/samples . Zapoznaj się z własnymi wzorcami i dodaj tę technikę do zwykłych działań kodowania. Uczenie się tych technik umożliwia inne podejście do problemów i tworzenie nowych funkcji.

Zobacz też