Samouczek: używanie dopasowywania wzorców do tworzenia algorytmów opartych na typach i opartych na danych
Możesz napisać funkcje, które zachowują się tak, jakby rozszerzone typy, które mogą znajdować się w innych bibliotekach. 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ć dopasowania wzorca.
- 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
- Zalecamy program Visual Studio dla systemu Windows. Bezpłatną wersję można pobrać ze strony pobierania programu Visual Studio. Program Visual Studio zawiera zestaw .NET SDK.
- Możesz również użyć edytora programu Visual Studio Code z zestawem deweloperskim języka C#. Musisz zainstalować oddzielnie najnowszy zestaw .NET SDK .
- Jeśli wolisz inny edytor, musisz zainstalować najnowszy zestaw .NET SDK.
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 korzystający z opłat i cen szczytu czasu 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 ulepszenia dodają ceny na podstawie 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 modelowania danych pojazdu z tych systemów zewnętrznych, jak 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.System.Object
Dopasowywanie wzorów
Scenariusz używany w tym samouczku wyróżnia rodzaje problemów, które pasują do wzorca są odpowiednie do rozwiązania:
- 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:
- A
Car
to $2.00. - A
Taxi
to $3.50. - A
Bus
to $5.00. - A
DeliveryTruck
to $10.00
Utwórz nową TollCalculator
klasę i zaimplementuj dopasowanie 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 (nie takiego samego jak switch
instrukcja), które testuje wzorzec deklaracji. Wyrażenie przełącznika zaczyna się od zmiennej w vehicle
poprzednim kodzie, po którym następuje switch
słowo kluczowe . Następnie przychodzi wszystkie ramiona przełącznika wewnątrz nawiasów klamrowych. Wyrażenie switch
wykonuje inne uściślenia składni, która otacza instrukcję switch
. Słowo case
kluczowe zostanie pominięte, a wynikiem każdego ramienia jest wyrażenie. Ostatnie dwa ramiona pokazują nową funkcję językową. Przypadek { }
pasuje do dowolnego obiektu innego niż null, który nie pasuje do wcześniejszego ramienia. To ramię przechwytuje 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 stałej wykrywa, null
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.
Dodawanie cennika zajętości
Urząd płatny chce zachęcić pojazdy do podróżowania z maksymalną pojemnością. 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ększą większa większa liczba pasażerów 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ą ponad 90% pełne dostać 1,00 dolarów zniżki.
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 rozszerzenia do Car
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:
- Ciężarówki powyżej 5000 funtów są naliczane dodatkowe 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,
};
Powyższy kod przedstawia klauzulę when
ramienia przełącznika. Klauzula służy do testowania when
warunków innych niż równość 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. Taxi
Obie Car
te elementy mają cztery różne ramiona w poprzednich przykładach. W obu przypadkach można utworzyć wzorzec deklaracji, który jest dostarczany do 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 Car
ramion i Taxi
zawierających ramiona podrzędne, które testują wartość właściwości. Ta technika nie jest używana dla Bus
ramion i DeliveryTruck
, ponieważ te ramiona testują zakresy dla właściwości, a nie dyskretne wartości.
Dodawanie szczytowych cen
W przypadku ostatniej funkcji urząd płatny chce dodać wrażliwe na czas ceny szczytowe. 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 połączyć łańcuch we wszystkich przypadkach wejściowych i zagnieżdżonych if
instrukcji, aby poznać przyczynę kodu. Zamiast tego użyjesz dopasowania wzorca dla tej funkcji, ale zintegrujesz ją 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 wzorca, 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 jest w mieście lub poza miastem
W poniższej tabeli przedstawiono kombinacje wartości wejściowych i mnożnik cen szczytowych:
Dzień | Czas | Kierunek | Premium |
---|---|---|---|
Weekday | poranny pośpiech | Przychodzących | x 2.00 |
Weekday | poranny pośpiech | wychodzący | x 1.00 |
Weekday | dzień | Przychodzących | x 1.50 |
Weekday | dzień | wychodzący | x 1.50 |
Weekday | wieczorny pośpiech | Przychodzących | x 1.00 |
Weekday | wieczorny pośpiech | wychodzący | x 2.00 |
Weekday | Noc | Przychodzących | x 0,75 |
Weekday | Noc | wychodzący | x 0,75 |
Weekend | poranny pośpiech | Przychodzących | x 1.00 |
Weekend | poranny pośpiech | wychodzący | x 1.00 |
Weekend | dzień | Przychodzących | x 1.00 |
Weekend | dzień | wychodzący | x 1.00 |
Weekend | wieczorny pośpiech | Przychodzących | x 1.00 |
Weekend | wieczorny pośpiech | wychodzący | x 1.00 |
Weekend | Noc | Przychodzących | x 1.00 |
Weekend | 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, korzysta ze DateTime struktury na czas zbierania opłat. Tworzenie metod składowych, które tworzą zmienne z poprzedniej tabeli. Poniższa funkcja używa wyrażenia przełącznika 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 wzorców sprzężeniaor
. Wzorzec relacyjny umożliwia testowanie wartości liczbowej przy użyciu wartości <
, , <=
>
lub >=
. Wzorzec sprawdza, or
czy wyrażenie jest zgodne z co najmniej jednym wzorcem. 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 switch
wyrażenia ze wzorcem krotki, aby obliczyć cenę premium. Możesz utworzyć switch
wyrażenie 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 w weekend mają te same opłaty. 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ąć dwa godziny szczytu, które płacą zwykłą cenę. Po usunięciu tych ramion można zastąpić go false
odrzuceniem (_
) w ostatnim ramieniu przełącznika. Uzyskasz następującą 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 rozmieścisz je tak, aby wcześniejsza gałąź obsługiwała jeden z Twoich późniejszych przypadków, kompilator ostrzega Cię o nieachomalnym 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 zużyliśmy istniejące dane na zupełnie inny sposób niż jego 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.