"Některé věci mají k sobě nepopsatelně blízko" - příkaz switch v jazyce C# a metoda String.IsInterned
Jazyk C# povoluje konstrukci switch, v níž se podmíněná sekce kódu zvolí podle shody proměnné typu string s konstantním řetězcem v sekci case.
switch (myVariable1 + myVariable2)
{
case “sekce1”:
{
//Zpracuj
break;
}
case “sekce 2”:
{
//Zpracuj
break;
}
default:
{
//Nedelej nic
break;
}
}
I když napsání swich výrazu s řetězcem je pro vývojáře jednoduchou záležitostí, zpracování switch výrazu kompilátorem již tak triviální není. Představte si, že porovnání shody řetězců by bylo prováděno znak po znaku. S prodloužením řetězců by docházelo k lineárnímu nárůstu doby potřebné pro porovnání řetězců. Jak asi ze své zkušenosti víte, příkaz case je vždy stejně rychlý bez ohledu na délku řetězců a k žádnému neohrabanému porovnávání řetězců znak po znaku v žádném případě nedochází. Jak je tedy příkaz case kompilátorem zpracován a rozvinut?
Všechny literály (string someString = “Ja jsem literal“) jsou při JIT kompilaci aplikace přidány do speciální hashovací tabulky vytvářené a spravované běhovým prostředím, v níž klíčem je řetězec a hodnotou odkaz (reference, adresa v paměti) na tento řetězec. Podstatné je, že řetězec se stejnou sekvencí znaků se v tabulce vyskytuje nanejvýš jednou. Takže pokud nadeklaruji kromě výše uvedené proměnné someString proměnnou someString2 a inicializuji ji stejným řetězcem jako proměnnou someString ( string someString2 = “Ja jsem literal“)), tak běhové prostředí při JIT kompilaci zjistí, že v hashovací tabulce klíč Ja jsem literal již existuje a proto do proměnné someString2 přiřadí odkaz na objekt umístěný pod tímto klíčem. Proměnné someString a someString2 odkazují poté na stejné místo v paměti, což si lze snadno ověřit voláním metody ReferenceEquals.
Z umístění do hashovací tabulky jsou při JIT kompilaci vyloučeny řetězce, jež jsou konstruovány dynamicky za běhu programu. Takže řetězec v proměnné string someString3 = “Ja jsem literal“ + someString2 přidán nebude. Třída String má ale statickou metodu Intern, jejím účelem je dodatečné vložení řetězce a odkazu na něj do hashovací tabulky. Příkazem String.Intern(someString3) je do hashovací tabulky přidán obsah proměnné someString3.
Když nechceme řetězec do hashovací tabulky přidat, ale chceme se jen dotázat, zda je v hashovací tabulce již obsažen, použijeme další statickou metody String.IsInterned. Metoda IsInterned při nalezení řetězce v hashovací tabulce vrací odkaz na tento řetězec. Když řetězec v hashovací tabulce není, metoda IsInterned vrátí null.
Nyní si už můžete sami odvodit, jak kompilátor efektivně zpracuje příkaz switch s řetězci. Po přechodu na příkaz switch je zjištěn proměnný rozhodovací řetězec, podle jehož hodnoty mají být zvoleny sekce case (switch(myVariable1 +myVariable2) ). Poté je volána metoda String. IsInterned, jíž je předán jako argument rozhodovací řetězec. Když metoda vrátí null, je jisté, že žádná sekce case nevyhovuje rozhodovacímu řetězci, protože všechny řetězce u case sekcí byly přidány do hashovací tabulky při JIT kompilaci. Jestliže metoda vrátí odkaz na řetězec, je testována shoda vráceného odkazu s odkazy řetězců u jednotlivých sekcí case. Když se reference řetězců shodují, je zvolena daná sekce case. Porovnávány jsou jen adresy řetězců v paměti (odkazy, reference), nikdy ne jednotlivé znaky, a proto rychlost provedení příkazu switch není nijak determinována délkou řetězců.
Monday, May 31, 2004 8:39:00 PM (Central Europe Standard Time, UTC+01:00)
.NET Framework