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.
V tomto kurzu se naučíte:
- Implementujte vzor zpracování interpolace řetězců.
- Interagujte s přijímačem v operaci řetězcové interpolace.
- Přidejte argumenty do obslužného zpracovatele interpolace řetězců.
- Seznamte se s novými funkcemi knihovny pro interpolaci řetězců.
Požadavky
Nastavte počítač tak, aby běžel .NET. Kompilátor jazyka C# je k dispozici prostřednictvím sady Visual Studio nebo sady .NET SDK.
V tomto kurzu se předpokládá, že znáte C# a .NET, včetně sady Visual Studio nebo Visual Studio Code a sady C# DevKit.
Můžete napsat vlastní obsluhu interpolovaných řetězců . Interpolovaná obslužná rutina řetězce je typ, který zpracovává zástupný výraz v interpolovaném řetězci. Bez vlastní obslužné rutiny systém zpracovává zástupné symboly podobné String.Format. Každý zástupný symbol je naformátovaný jako text a potom jsou komponenty zřetězeny tak, aby vytvořily výsledný řetězec.
Obslužnou rutinu můžete napsat pro libovolný scénář, ve kterém použijete informace o výsledném řetězci. Zvažte otázky jako: Používá se? Jaká omezení jsou ve formátu? Mezi příklady patří:
- Můžete vyžadovat, aby žádný z výsledných řetězců nebyl větší než nějaký limit, například 80 znaků. Můžete zpracovat interpolované řetězce, aby vyplnily vyrovnávací paměť s pevnou délkou, a zpracování zastavit, jakmile je této délky dosaženo.
- Je možné, že máte tabulkový formát a každý zástupný symbol musí mít pevnou délku. Vlastní obslužná rutina může toto omezení vynutit, nikoli vynutit, aby byl veškerý klientský kód v souladu.
V tomto kurzu vytvoříte obslužnou rutinu interpolace řetězců pro jeden ze základních scénářů výkonu: knihovny protokolování. V závislosti na nakonfigurované úrovni protokolu není potřeba vytvořit zprávu protokolu. Pokud je protokolování vypnuté, není potřeba vytvořit řetězec z interpolovaného řetězcového výrazu. Zpráva se nikdy nevytiskne, takže je možné vynechat zřetězení řetězce. Kromě toho není nutné provádět všechny výrazy použité v zástupných symbolech, včetně generování trasování zásobníku.
Interpolovaná obslužná rutina řetězců může určit, jestli se formátovaný řetězec používá, a v případě potřeby provést pouze potřebnou práci.
Počáteční implementace
Začněte základní Logger třídou, která podporuje různé úrovně:
public enum LogLevel
{
Off,
Critical,
Error,
Warning,
Information,
Trace
}
public class Logger
{
public LogLevel EnabledLevel { get; init; } = LogLevel.Error;
public void LogMessage(LogLevel level, string msg)
{
if (EnabledLevel < level) return;
Console.WriteLine(msg);
}
}
Tato Logger podporuje šest různých úrovní. Pokud zpráva nepřejde filtrem na úrovni protokolu, protokolovací služba nevygeneruje žádný výstup. Veřejné rozhraní API pro protokolovací modul přijímá jako zprávu plně formátovaný řetězec. Volající udělá vše potřebné pro vytvoření řetězce.
Implementace vzoru obslužné rutiny
V tomto kroku vytvoříte obsluhu interpolovaného řetězce, která reprodukuje stávající chování. Handlery interpolovaných řetězců jsou typy, které musí mít následující vlastnosti:
- System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute použitý u typu.
- Konstruktor, který má dva parametry
int,literalLengthaformattedCount. (Jsou povoleny další parametry). - Veřejná metoda
AppendLiterals podpisem:public void AppendLiteral(string s). - Obecná veřejná
AppendFormattedmetoda s deklarací:public void AppendFormatted<T>(T t).
Tvůrce interně vytvoří formátovaný řetězec a poskytne mechanismus pro klienta k získání daného řetězce. Následující kód ukazuje typ LogInterpolatedStringHandler, který splňuje tyto požadavky:
[InterpolatedStringHandler]
public struct LogInterpolatedStringHandler
{
// Storage for the built-up string
StringBuilder builder;
public LogInterpolatedStringHandler(int literalLength, int formattedCount)
{
builder = new StringBuilder(literalLength);
Console.WriteLine($"\tliteral length: {literalLength}, formattedCount: {formattedCount}");
}
public void AppendLiteral(string s)
{
Console.WriteLine($"\tAppendLiteral called: {{{s}}}");
builder.Append(s);
Console.WriteLine($"\tAppended the literal string");
}
public void AppendFormatted<T>(T t)
{
Console.WriteLine($"\tAppendFormatted called: {{{t}}} is of type {typeof(T)}");
builder.Append(t?.ToString());
Console.WriteLine($"\tAppended the formatted object");
}
public override string ToString() => builder.ToString();
}
Poznámka:
Pokud je interpolovaný řetězcový výraz konstantou v době kompilace (tj. neobsahuje žádné zástupné symboly), kompilátor používá cílový typ string místo vyvolání vlastní interpolované obslužné rutiny řetězců. Toto chování znamená, že konstantní interpolované řetězce zcela obcházejí vlastní obslužné rutiny.
Teď můžete definovat přetížení pro LogMessage ve třídě Logger, abyste vyzkoušeli nový interpolovaný obslužný mechanismus řetězce.
public void LogMessage(LogLevel level, LogInterpolatedStringHandler builder)
{
if (EnabledLevel < level) return;
Console.WriteLine(builder.ToString());
}
Původní metodu LogMessage nemusíte odebírat. Pokud je argument interpolovaným řetězcovým výrazem, kompilátor preferuje metodu s interpolovaným parametrem obslužné rutiny před metodou s parametrem string .
Novou obslužnou rutinu můžete ověřit pomocí následujícího kódu v hlavním programu:
var logger = new Logger() { EnabledLevel = LogLevel.Warning };
var time = DateTime.Now;
logger.LogMessage(LogLevel.Error, $"Error Level. CurrentTime: {time}. This is an error. It will be printed.");
logger.LogMessage(LogLevel.Trace, $"Trace Level. CurrentTime: {time}. This won't be printed.");
logger.LogMessage(LogLevel.Warning, "Warning Level. This warning is a string, not an interpolated string expression.");
Spuštění aplikace vytvoří výstup podobný následujícímu textu:
literal length: 65, formattedCount: 1
AppendLiteral called: {Error Level. CurrentTime: }
Appended the literal string
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type System.DateTime
Appended the formatted object
AppendLiteral called: {. This is an error. It will be printed.}
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
AppendLiteral called: {Trace Level. CurrentTime: }
Appended the literal string
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type System.DateTime
Appended the formatted object
AppendLiteral called: {. This won't be printed.}
Appended the literal string
Warning Level. This warning is a string, not an interpolated string expression.
Při procházení výstupem můžete vidět, jak kompilátor přidává kód k volání obslužné rutiny a sestavení řetězce.
- Kompilátor přidá volání pro vytvoření zpracovatele, přičemž předá celkovou délku literálového textu ve formátovacím řetězci a počet zástupných symbolů.
- Kompilátor přidá volání
AppendLiteralaAppendFormattedpro každou část literálového řetězce a pro každý zástupný symbol. - Kompilátor vyvolá metodu
LogMessagepomocíCoreInterpolatedStringHandlerjako argumentu.
Nakonec si všimněte, že poslední upozornění nevyužívá interpolovanou obslužnou rutinu řetězce. Argument je string, takže volání vyvolá druhou přetíženou funkci s řetězcovým parametrem.
Důležitý
Pro interpolované obslužné rutiny řetězců používejte ref struct pouze tehdy, když je to naprosto nezbytné.
ref struct typy mají omezení, protože musí být uloženy na stacku. Například nefungují, pokud interpolovaný řetězcový otvor obsahuje await výraz, protože kompilátor potřebuje uložit obslužnou rutinu do implementace generované IAsyncStateMachine kompilátorem.
Přidání dalších funkcí do obslužné rutiny
Předchozí verze obslužné rutiny pro interpolované řetězce implementuje vzor. Abyste se vyhnuli zpracování každého zástupného výrazu, potřebujete další informace v obslužném programu. V této části vylepšíte obslužnou rutinu tak, aby méně fungovala, když se vytvořený řetězec nezapíše do protokolu. Pomocí System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute určíte mapování mezi parametry veřejného rozhraní API a parametry konstruktoru obslužné rutiny. Toto mapování poskytuje zpracovateli informace potřebné k určení, jestli má být interpolovaný řetězec vyhodnocen.
Začněte se změnami obslužné rutiny. Nejprve přidejte pole ke sledování, jestli je obslužná rutina povolená. Přidejte do konstruktoru dva parametry: jeden pro zadání úrovně protokolu pro tuto zprávu a druhý odkaz na objekt protokolu:
private readonly bool enabled;
public LogInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, LogLevel logLevel)
{
enabled = logger.EnabledLevel >= logLevel;
builder = new StringBuilder(literalLength);
Console.WriteLine($"\tliteral length: {literalLength}, formattedCount: {formattedCount}");
}
Dále použijte pole, aby obslužná rutina při použití konečného řetězce připojila pouze literály nebo naformátované objekty:
public void AppendLiteral(string s)
{
Console.WriteLine($"\tAppendLiteral called: {{{s}}}");
if (!enabled) return;
builder.Append(s);
Console.WriteLine($"\tAppended the literal string");
}
public void AppendFormatted<T>(T t)
{
Console.WriteLine($"\tAppendFormatted called: {{{t}}} is of type {typeof(T)}");
if (!enabled) return;
builder.Append(t?.ToString());
Console.WriteLine($"\tAppended the formatted object");
}
Dále aktualizujte LogMessage deklaraci tak, aby kompilátor předal další parametry konstruktoru obslužné rutiny. Pomocí obslužného argumentu System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute zpracujete tento krok:
public void LogMessage(LogLevel level, [InterpolatedStringHandlerArgument("", "level")] LogInterpolatedStringHandler builder)
{
if (EnabledLevel < level) return;
Console.WriteLine(builder.ToString());
}
Tento atribut určuje seznam argumentů, které mapují na parametry, jež následují za povinnými parametry literalLength a formattedCount. Prázdný řetězec ("") určuje příjemce. Kompilátor nahradí další argument konstruktoru obslužné rutiny hodnotou objektu Logger reprezentovanou pomocí this. Kompilátor nahradí hodnotu level následujícím argumentem. Můžete zadat libovolný počet argumentů pro jakoukoli obslužnou rutinu, kterou napíšete. Argumenty, které přidáte, jsou řetězcové argumenty.
Poznámka:
InterpolatedStringHandlerArgumentAttribute Pokud je seznam argumentů konstruktoru prázdný, chování je stejné jako v případě, že byl atribut vynechán zcela.
Tuto verzi můžete spustit pomocí stejného testovacího kódu. Tentokrát se zobrazí následující výsledky:
literal length: 65, formattedCount: 1
AppendLiteral called: {Error Level. CurrentTime: }
Appended the literal string
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type System.DateTime
Appended the formatted object
AppendLiteral called: {. This is an error. It will be printed.}
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
AppendLiteral called: {Trace Level. CurrentTime: }
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type System.DateTime
AppendLiteral called: {. This won't be printed.}
Warning Level. This warning is a string, not an interpolated string expression.
Vidíte, že se volají metody AppendLiteral a AppendFormat, ale neprovádějí žádnou práci. Obslužná rutina zjistila, že konečný řetězec není potřeba, takže obslužná rutina ho nevytvoře. Stále existuje několik vylepšení.
Nejprve můžete přidat přetížení AppendFormatted, které omezuje argument na typ, který implementuje System.IFormattable. Toto přetížení umožňuje volajícím přidávat do zástupných symbolů formátovací řetězce. Při provádění této změny také změňte návratový typ ostatních AppendFormatted a AppendLiteral metod z void na bool. Pokud některé z těchto metod mají různé návratové typy, zobrazí se chyba kompilace. Tato změna umožňuje zkrat . Metody vracejí false označující, že zpracování interpolovaného řetězcového výrazu by mělo být zastaveno. Vrácení true značí, že se má pokračovat. V tomto příkladu ho používáte k zastavení zpracování v případě, že výsledný řetězec není potřeba. Zkratování podporuje jemněji odstupňované akce. Můžete zastavit zpracování výrazu, jakmile dosáhne určité délky, a tím podpořit vyrovnávací paměti s pevnou délkou. Nebo některá podmínka může znamenat, že zbývající prvky nejsou potřeba.
public void AppendFormatted<T>(T t, string format) where T : IFormattable
{
Console.WriteLine($"\tAppendFormatted (IFormattable version) called: {t} with format {{{format}}} is of type {typeof(T)},");
builder.Append(t?.ToString(format, null));
Console.WriteLine($"\tAppended the formatted object");
}
public void AppendFormatted<T>(T t, int alignment, string format) where T : IFormattable
{
Console.WriteLine($"\tAppendFormatted (IFormattable version) called: {t} with alignment {alignment} and format {{{format}}} is of type {typeof(T)},");
var formatString =$"{alignment}:{format}";
builder.Append(string.Format($"{{0,{formatString}}}", t));
Console.WriteLine($"\tAppended the formatted object");
}
Díky tomuto rozšíření můžete ve výrazu interpolovaného řetězce specifikovat řetězce formátu.
var time = DateTime.Now;
logger.LogMessage(LogLevel.Error, $"Error Level. CurrentTime: {time}. The time doesn't use formatting.");
logger.LogMessage(LogLevel.Error, $"Error Level. CurrentTime: {time:t}. This is an error. It will be printed.");
logger.LogMessage(LogLevel.Trace, $"Trace Level. CurrentTime: {time:t}. This won't be printed.");
:t v první zprávě označuje "krátký časový formát" pro aktuální čas. Předchozí příklad ukázal jedno z přetížení metody AppendFormatted, které můžete vytvořit pro svou obslužnou rutinu. Pro formátovaný objekt nemusíte zadávat obecný argument. Možná máte efektivnější způsoby převodu typů, které vytvoříte na řetězec. Můžete vytvářet přetížení AppendFormatted, která přijímají tyto typy místo generického argumentu. Kompilátor vybere nejlepší přetížení. Modul runtime používá tuto techniku k převodu System.Span<T> na výstup řetězce. Můžete přidat celočíselný parametr k určení zarovnání výstupu, s nebo bez IFormattable.
System.Runtime.CompilerServices.DefaultInterpolatedStringHandler, která se dodává s rozhraním .NET 6, obsahuje devět přetížení AppendFormatted pro různá použití. Můžete ho použít jako referenci při vytváření obslužné rutiny pro vaše účely.
Spusťte ukázku a uvidíte, že pro zprávu Trace se volá pouze první AppendLiteral:
literal length: 60, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted called: 10/20/2021 12:18:29 PM is of type System.DateTime
Appended the formatted object
AppendLiteral called: . The time doesn't use formatting.
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:18:29 PM. The time doesn't use formatting.
literal length: 65, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted (IFormattable version) called: 10/20/2021 12:18:29 PM with format {t} is of type System.DateTime,
Appended the formatted object
AppendLiteral called: . This is an error. It will be printed.
Appended the literal string
Error Level. CurrentTime: 12:18 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
AppendLiteral called: Trace Level. CurrentTime:
Warning Level. This warning is a string, not an interpolated string expression.
Můžete provést jednu poslední aktualizaci konstruktoru zpracovatele, která zvyšuje efektivitu. Obslužná rutina může přidat konečný parametr out bool. Nastavení parametru na false znamená, že obslužná rutina by neměla být volána vůbec, aby bylo možné zpracovat interpolovaný řetězcový výraz:
public LogInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, LogLevel level, out bool isEnabled)
{
isEnabled = logger.EnabledLevel >= level;
Console.WriteLine($"\tliteral length: {literalLength}, formattedCount: {formattedCount}");
builder = isEnabled ? new StringBuilder(literalLength) : default!;
}
Tato změna znamená, že pole enabled můžete odebrat. Potom můžete změnit návratový typ AppendLiteral a AppendFormatted na void.
Když teď ukázku spustíte, zobrazí se následující výstup:
literal length: 60, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted called: 10/20/2021 12:19:10 PM is of type System.DateTime
Appended the formatted object
AppendLiteral called: . The time doesn't use formatting.
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. The time doesn't use formatting.
literal length: 65, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted (IFormattable version) called: 10/20/2021 12:19:10 PM with format {t} is of type System.DateTime,
Appended the formatted object
AppendLiteral called: . This is an error. It will be printed.
Appended the literal string
Error Level. CurrentTime: 12:19 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
Warning Level. This warning is a string, not an interpolated string expression.
Jediným výstupem při zadání LogLevel.Trace je výstup z konstruktoru. Obslužná rutina značí, že není povolená, takže se nevyvolá žádná z Append metod.
Tento příklad ukazuje důležitý bod pro interpolaci řetězců, zejména při použití knihoven pro logování. U zástupných symbolů nemusí dojít k žádným vedlejším efektům. Do hlavního programu přidejte následující kód a podívejte se na toto chování v akci:
int index = 0;
int numberOfIncrements = 0;
for (var level = LogLevel.Critical; level <= LogLevel.Trace; level++)
{
Console.WriteLine(level);
logger.LogMessage(level, $"{level}: Increment index {index++}");
numberOfIncrements++;
}
Console.WriteLine($"Value of index {index}, value of numberOfIncrements: {numberOfIncrements}");
Vidíte, že index proměnná se zvýší při každé iteraci smyčky. Vzhledem k tomu, že zástupné symboly se vyhodnocují pouze pro Critical, Errora Warning úrovně, nikoli pro Informationa Trace, konečná hodnota index neodpovídá očekávání:
Critical
Critical: Increment index 0
Error
Error: Increment index 1
Warning
Warning: Increment index 2
Information
Trace
Value of index 3, value of numberOfIncrements: 5
Interpolované zpracovatele řetězců poskytují větší kontrolu nad tím, jak je interpolovaný výraz řetězce převeden na řetězec. Tým modulu runtime .NET tuto funkci použil ke zlepšení výkonu v několika oblastech. Stejnou funkci můžete využít ve svých vlastních knihovnách. Pokud chcete prozkoumat další informace, podívejte se na System.Runtime.CompilerServices.DefaultInterpolatedStringHandler. Poskytuje ucelenější implementaci, než jste zde vytvořili. Uvidíte mnoho dalších přetížení, které jsou možné pro Append metody.