Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Můžete psát funkce, které se chovají jako rozšířené typy, které mohou být v jiných knihovnách. Další možností použití vzorů je vytvoření funkcí, které vaše aplikace vyžaduje, aby nebyla základní funkcí typu, který se rozšiřuje.
V tomto kurzu se naučíte:
- Rozpoznávejte situace, kdy se má použít porovnávání vzorů.
- Pomocí výrazů porovnávání vzorů můžete implementovat chování na základě typů a hodnot vlastností.
- Zkombinujte porovnávání vzorů s jinými technikami a vytvořte kompletní algoritmy.
Požadavky
- Nejnovější sada .NET SDK
- editor Visual Studio Code editoru
- C# DevKit
Pokyny k instalaci
Ve Windows použijte tento konfigurační soubor WinGet k instalaci všech předpokladů. Pokud už máte něco nainstalovaného, WinGet tento krok přeskočí.
- Stáhněte soubor a poklikáním ho spusťte.
- Přečtěte si licenční smlouvu, zadejte ya po zobrazení výzvy k přijetí vyberte Enter.
- Pokud se na hlavním panelu zobrazí výzva řízení uživatelských účtů (UAC), povolte instalaci pokračovat.
Na jiných platformách je potřeba nainstalovat každou z těchto komponent samostatně.
- Stáhněte si doporučený instalační program ze stránky pro stažení .NET SDK a poklikáním ho spusťte. Stránka pro stažení zjistí vaši platformu a doporučí nejnovější instalační program pro vaši platformu.
- Stáhněte si nejnovější instalační program z domovské stránky editoru Visual Studio Code a dvojitým kliknutím ho spusťte. Tato stránka také zjistí vaši platformu a odkaz by měl být pro váš systém správný.
- Na stránce rozšíření C# DevKit klikněte na tlačítko Nainstalovat. Tím se otevře Visual Studio Code a zobrazí se dotaz, jestli chcete rozšíření nainstalovat nebo povolit. Vyberte "nainstalovat".
V tomto kurzu se předpokládá, že znáte C# a .NET, včetně sady Visual Studio nebo rozhraní příkazového řádku .NET.
Scénáře pro porovnávání vzorů
Moderní vývoj často zahrnuje integraci dat z více zdrojů a prezentování informací a přehledů z dat v jedné soudržné aplikaci. Vy a váš tým nebudete mít kontrolu ani přístup pro všechny typy, které představují příchozí data.
Klasický objektově orientovaný návrh by vyžadoval vytváření typů ve vaší aplikaci, které představují každý datový typ z těchto různorodých zdrojů dat. Aplikace pak bude pracovat s těmito novými typy, vytvářet hierarchie dědičnosti, vytvářet virtuální metody a implementovat abstrakce. Tyto techniky fungují a někdy jsou nejlepšími nástroji. Jindy můžete napsat méně kódu. Srozumitelnější kód můžete napsat pomocí technik, které oddělují data od operací, které s nimi manipulují.
V tomto kurzu vytvoříte a prozkoumáte aplikaci, která přijímá příchozí data z několika externích zdrojů pro jeden scénář. Uvidíte, jak porovnávání vzorů představuje efektivní způsob, jak tato data využívat a zpracovávat způsoby, které nebyly součástí původního systému.
Zvažte hlavní metropolitní oblast, která ke správě provozu využívá placené poplatky a ceny ve špičce. Píšete aplikaci, která počítá mýtné pro vozidlo na základě jeho typu. Pozdější vylepšení zahrnují ceny založené na počtu osob ve vozidle. Další vylepšení přidávají ceny na základě času a dne v týdnu.
Z tohoto stručného popisu jste možná rychle načrtli hierarchii objektů pro modelování tohoto systému. Vaše data ale pocházejí z více zdrojů, jako jsou jiné systémy správy registrace vozidel. Tyto systémy poskytují různé třídy pro modelování těchto dat a nemáte jediný objektový model, který můžete použít. V tomto kurzu použijete tyto zjednodušené třídy k modelování dat vozidel z těchto externích systémů, jak je znázorněno v následujícím kódu:
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; }
}
}
Počáteční kód si můžete stáhnout z úložiště dotnet/samples na GitHubu. Vidíte, že třídy vozidel pocházejí z různých systémů a jsou v různých jmenných prostorech. Nelze použít žádnou společnou základní třídu, kromě System.Object.
Návrhy pro shodu vzorů
Scénář použitý v tomto kurzu zvýrazňuje druhy problémů, které porovnávání vzorů je vhodné k vyřešení:
- Objekty, se kterými potřebujete pracovat, nejsou v hierarchii objektů, které odpovídají vašim cílům. Možná pracujete s třídami, které jsou součástí nesouvisejících systémů.
- Funkce, které přidáváte, nejsou součástí základní abstrakce těchto tříd. Mýtné placené vozidlem se liší podle různých typů vozidel, ale mýtné není hlavní funkcí vozidla.
Pokud tvar dat a operací s daty nejsou popsány společně, funkce porovnávání vzorů v jazyce C# usnadňují práci.
Implementace základních výpočtů mýtného
Nejzákladnější výpočet mýtného je založen pouze na typu vozidla.
- A
Carje $2,00. - A
Taxije $3,50. - A
Busje 5,00 USD. - A
DeliveryTruckje $10,00
Vytvořte novou TollCalculator třídu a implementujte vzorové porovnávání u typu vozidla, abyste získali výši mýtného. Následující kód ukazuje počáteční implementaci 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))
};
}
Předchozí kód používá switch výraz (ne stejný jako switch příkaz), který testuje vzor deklarace.
Příkaz switch začíná proměnnou vehicle v předchozím kódu, kterou následuje klíčové slovo switch. Dále se objevují všechny přepínací ramena uvnitř složených závorek. Výraz switch provádí další úpravy syntaxe obklopující příkaz switch. Klíčové case slovo je vynecháno a výsledek každé větve je výraz. Poslední dvě ramena ukazují novou funkci jazyka. Případ { } odpovídá jakémukoli nenulovému objektu, který neodpovídal žádnému dřívějšímu “armu”. Tato součást zachytí všechny nesprávné typy předané této metodě. Případ { } musí odpovídat případům pro každý typ vozidla. Pokud by bylo pořadí obrácené, { } případ by měl přednost. Nakonec konstantní nullvzor zjistí, kdy null je předána této metodě. Vzor null může být poslední, protože ostatní vzory odpovídají pouze objektu, který není null správného typu.
Tento kód můžete otestovat pomocí následujícího kódu v 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");
}
Tento kód je součástí počátečního projektu, ale je zakomentován. Odeberte komentáře a můžete otestovat, co jste napsali.
Začínáte vidět, jak vám vzory můžou pomoct vytvářet algoritmy, ve kterých je kód a data oddělená. Výraz switch testuje typ a vytváří různé hodnoty na základě výsledků. To je jen začátek.
Přidat ceny za obsazenost
Mýtná správa chce povzbuzovat vozidla k jízdě v maximálním obsazení. Rozhodli se účtovat více, když vozidla mají méně cestujících, a podpořit plné vozidla tím, že nabízejí nižší ceny:
- Auta a taxíky bez cestujících platí navíc 0,50 USD.
- Auta a taxi se dvěma cestujícími získají slevu 0,50 USD.
- Auta a taxi se třemi nebo více cestujícími získají slevu 1,00 USD.
- Autobusy, které jsou méně než z 50% obsazené, platí navíc 2,00 $.
- Autobusy, které jsou více než 90% plné, získají slevu 1,00 USD.
Tato pravidla je možné implementovat pomocí vzoru vlastnosti ve stejném výrazu switch. Vzor vlastnosti porovnává hodnotu vlastnosti s konstantní hodnotou. Vzor vlastnosti zkoumá vlastnosti objektu po určení typu. Jeden případ se rozšiřuje na čtyři různé případy:
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,
// ...
};
První tři případy testují typ jako Car, pak zkontrolují hodnotu vlastnosti Passengers. Pokud se oba parametry shodují, tento výraz se vyhodnotí a vrátí.
Případy taxislužby byste také rozšířili podobným způsobem:
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,
// ...
};
Dále implementujte pravidla obsazenosti rozšířením případů pro autobusy, jak je znázorněno v následujícím příkladu:
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,
// ...
};
Mýtná správa se nezajímá o počet osob v dodávkových vozech. Místo toho upraví částku mýtného na základě hmotnostní třídy nákladních vozidel následujícím způsobem:
- Nákladní vozy nad 5000 lb se účtují navíc 5,00 USD.
- Lehká nákladní vozidla pod 1360 kg mají slevu 2,00 USD.
Toto pravidlo se implementuje s následujícím kódem:
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,
};
Předchozí kód ukazuje klauzuli when větve přepínače. Klauzuli when použijete k testování podmínek jiné než rovnosti u vlastnosti. Po dokončení budete mít metodu, která vypadá podobně jako následující kód:
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))
};
Mnohé z těchto přepínačů představují příklady rekurzivních vzorů. Například Car { Passengers: 1} ukazuje konstantní vzor uvnitř vzoru vlastnosti.
Tento kód můžete udělat méně opakujícím se použitím vnořených přepínačů.
Car a Taxi mají oba čtyři různá ramena v předchozích příkladech. V obou případech můžete vytvořit vzor deklarace, který se propojí se vzorem konstanty. Tato technika se zobrazuje v následujícím kódu:
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))
};
V předchozím příkladu použití rekurzivního výrazu znamená, že neopakujete Car a Taxi větve obsahující podřízené větve, které testují hodnotu vlastnosti. Tato technika se nepoužívá pro ramena Bus a DeliveryTruck, protože tato ramena testují rozsahy pro vlastnost, nikoli diskrétní hodnoty.
Přidání cen ve špičce
U poslední funkce chce mýtný úřad přidat časově citlivé špičkové ceny. Během ranního a večerního spěchu se poplatky zdvojnásobí. Toto pravidlo má vliv jenom na provoz v jednom směru: příchozí do města ráno a odchozí v hodině večerního spěchu. V jiných časech během pracovního dne se placené poplatky zvyšují o 50 %. V noci a časně ráno se poplatky snižují o 25 %. Během víkendu je to normální sazba bez ohledu na čas. K tomuto můžete použít následující kód pomocí série příkazů if a else:
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;
}
}
}
Předchozí kód funguje správně, ale není čitelný. Je potřeba procházet všechny vstupní případy a vnořené příkazy if, abyste mohli kód analyzovat. Místo toho použijete porovnávání vzorů pro tuto funkci, ale integrujete ho s jinými technikami. Můžete vytvořit výraz shody s jedním vzorem, který by odpovídal všem kombinacím směru, dne v týdnu a času. Výsledkem by byl složitý výraz. Bylo by těžké číst a obtížně pochopit. To znesnadňuje zajištění správnosti. Místo toho tyto metody zkombinujte, abyste vytvořili řazenou kolekci hodnot, které stručně popisují všechny tyto stavy. Poté pomocí vyhledávání vzorů vypočítejte násobitel pro mýtné. N-tice obsahuje tři diskrétní podmínky.
- Den je buď pracovní den, nebo víkend.
- Časové období, kdy se vybírá mýto.
- Směr je do města nebo mimo město.
Následující tabulka ukazuje kombinace vstupních hodnot a násobitele cen ve špičce:
| Den | Čas | Směr | Prémiový |
|---|---|---|---|
| pracovní den | ranní spěch | příchozí | x 2,00 |
| pracovní den | ranní spěch | odchozí | x 1,00 |
| pracovní den | denní doba | příchozí | x 1,50 |
| pracovní den | denní doba | odchozí | x 1,50 |
| pracovní den | večerní spěch | příchozí | x 1,00 |
| pracovní den | večerní spěch | odchozí | x 2,00 |
| pracovní den | přes noc | příchozí | x 0,75 |
| pracovní den | přes noc | odchozí | x 0,75 |
| Víkend | ranní spěch | příchozí | x 1,00 |
| Víkend | ranní spěch | odchozí | x 1,00 |
| Víkend | denní doba | příchozí | x 1,00 |
| Víkend | denní doba | odchozí | x 1,00 |
| Víkend | večerní spěch | příchozí | x 1,00 |
| Víkend | večerní spěch | odchozí | x 1,00 |
| Víkend | přes noc | příchozí | x 1,00 |
| Víkend | přes noc | odchozí | x 1,00 |
Existují 16 různých kombinací těchto tří proměnných. Kombinováním některých podmínek zjednodušíte výsledný výraz switch.
Systém, který vybírá mýtné, používá DateTime strukturu pro zaznamenání času, kdy bylo mýtné vybráno. Vytvořte metody členů, které vytvářejí proměnné z předchozí tabulky. Následující funkce používá výraz přepínače porovnávání vzorů k vyjádření, zda DateTime představuje víkend nebo pracovní den:
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
};
Tato metoda je správná, ale je opakující se. Můžete ho zjednodušit, jak je znázorněno v následujícím kódu:
private static bool IsWeekDay(DateTime timeOfToll) =>
timeOfToll.DayOfWeek switch
{
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false,
_ => true
};
Dále přidejte podobnou funkci, která do bloků zařadí čas do kategorií:
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,
};
Přidáte soukromou enum hodnotu, která převede každý časový rozsah na diskrétní hodnotu.
GetTimeBand Metoda pak používá relační vzory a konjunkční or vzory. Relační vzor umožňuje testovat číselnou hodnotu pomocí <, >, <=nebo >=. Vzor or testuje, jestli výraz odpovídá jednomu nebo více vzorům. Pomocí vzoru můžete také and zajistit, aby výraz odpovídal dvěma odlišným vzorům a vzor not k otestování, že výraz neodpovídá vzoru.
Po vytvoření těchto metod můžete použít jiný switch výraz se vzorem n-tice k výpočtu cenového příplatku. Můžete vytvořit switch výraz se všemi 16 větvemi:
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,
};
Výše uvedený kód funguje, ale dá se zjednodušit. Všech osm kombinací na víkend má stejný poplatek. Všech osm můžete nahradit následujícím řádkem:
(false, _, _) => 1.0m,
Příchozí i odchozí provoz mají stejný násobitel během dne v týdnu a přes noc. Tyto čtyři přepínací ramena mohou být nahrazeny následujícími dvěma řádky:
(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _) => 1.5m,
Kód by měl po těchto dvou změnách vypadat jako následující kód:
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,
};
Nakonec můžete odebrat dvě doby dopravní špičky, pro které se účtuje běžná cena. Jakmile tato ramena odeberete, můžete je nahradit false zahozením (_) v posledním rameni přepínače. Budete mít následující dokončenou metodu:
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,
};
Tento příklad zvýrazní jednu z výhod porovnávání vzorů: větve vzorů se vyhodnocují v pořadí. Pokud je přeuspořádáte tak, aby starší větev zpracovávala jeden z vašich pozdějších případů, kompilátor vás upozorní na nedostupný kód. Tato jazyková pravidla usnadňují předchozí zjednodušení s jistotou, že se kód nezměnil.
Porovnávání vzorů zpřístupňuje některé typy kódu čitelnějším a nabízí alternativu k metodám orientovaným na objekty, když do tříd nemůžete přidat kód. Cloud způsobuje, že data a funkce žijí odděleně. Tvar dat a operací na něm nejsou nutně popsány společně. V tomto kurzu jste spotřebovali existující data úplně jinak než původní funkce. Porovnávání vzorů vám umožnilo psát funkce, které tyto typy přebíjejí, i když jste je nemohli rozšířit.
Další kroky
Hotový kód si můžete stáhnout z úložiště dotnet/samples na GitHubu. Prozkoumejte vzory sami a přidejte tuto techniku do běžných programovacích aktivit. Učení těchto technik vám dává další způsob, jak přistupovat k problémům a vytvářet nové funkce.