Megosztás a következőn keresztül:


Oktatóanyag: Egyéni sztring interpolációkezelő írása

Ebben az oktatóanyagban a következőket sajátíthatja el:

  • A sztring interpolációs kezelőjének mintájának implementálása
  • Egy sztring interpolációs művelet során lépjen kapcsolatba a vevővel.
  • Argumentumok hozzáadása a sztring interpolációkezelőhöz
  • A sztringinterpoláció új kódtár-funkcióinak ismertetése

Előfeltételek

Be kell állítania a gépet a .NET futtatására. A C# fordító a Visual Studio 2022-szel vagy a .NET SDK-mal érhető el.

Ez az oktatóanyag feltételezi, hogy ismeri a C# és a .NET használatát, beleértve a Visual Studiót vagy a .NET CLI-t is.

Egyéni interpolált sztringkezelőtírhat. Az interpolált sztringkezelő olyan típus, amely egy interpolált sztringben dolgozza fel a helyőrző kifejezést. Egyéni kezelő nélkül a helyőrzőket hasonlóan dolgozzák fel, mint a String.Format-et. Minden helyőrző szövegként van formázva, majd az összetevők összefűzve alkotják az eredményül kapott sztringet.

Bármely olyan forgatókönyvhöz írhat kezelőt, amelyben az eredményül kapott sztringre vonatkozó információkat használja. Használni fogja? Milyen korlátozások vannak a formátumon? Néhány példa:

  • Lehet, hogy szükséges, hogy az eredményül kapott sztringek egyike sem legyen nagyobb egy korlátnál, például 80 karakternél. Az interpolált sztringeket feldolgozhatja egy rögzített hosszúságú puffer kitöltéséhez, és a feldolgozást abbahagyhatja, amint a puffer hosszát eléri.
  • Lehet, hogy táblázatos formátumú, és minden helyőrzőnek rögzített hosszúságúnak kell lennie. Egy egyéni kezelő képes ezt érvényesíteni, ehelyett nem szükséges az összes ügyfélkódot megfelelőségre kényszeríteni.

Ebben az oktatóanyagban az egyik alapvető teljesítményscenárióhoz, a naplózó könyvtárakhoz hoz létre egy sztring interpolációs kezelőt. A konfigurált naplószinttől függően nincs szükség naplóüzenet létrehozására. Ha a naplózás ki van kapcsolva, nincs szükség egy karakterlánc interpolált sztringkifejezésből való összeállítására. Az üzenet soha nem lesz kinyomtatva, így a sztringösszefűzés kihagyható. Emellett a helyőrzőkben használt kifejezéseket, beleértve a verem nyomkövetését is, nem kell elvégezni.

Egy interpolált sztringkezelő meg tudja állapítani, hogy a formázott sztring lesz-e használva, és csak akkor végezze el a szükséges munkát, ha szükséges.

Kezdeti megvalósítás

Kezdjük egy alapszintű Logger osztálysal, amely különböző szinteket támogat:

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);
    }
}

Ez a Logger hat különböző szintet támogat. Ha egy üzenet nem adja át a naplószintű szűrőt, nincs kimenet. A naplózó nyilvános API-ja egy (teljesen formázott) sztringet fogad el üzenetként. A karakterlánc létrehozására irányuló összes munka már befejeződött.

A kezelőminta implementálása

Ez a lépés egy interpolált sztringkezelő létrehozása, amely újra létrehozza az aktuális viselkedést. Az interpolált sztringkezelő olyan típus, amely a következő jellemzőkkel rendelkezik:

  • A System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute a típusra lett alkalmazva.
  • Két int paraméterrel rendelkező konstruktor, literalLength és formattedCount. (További paraméterek engedélyezettek).
  • Nyilvános AppendLiteral metódus aláírással: public void AppendLiteral(string s).
  • Általános nyilvános AppendFormatted metódus aláírással: public void AppendFormatted<T>(T t).

A szerkesztő belsőleg létrehozza a formázott sztringet, és egy tagot biztosít az ügyfélnek a sztring lekéréséhez. Az alábbi kód egy LogInterpolatedStringHandler típust mutat be, amely megfelel az alábbi követelményeknek:

[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");
    }

    internal string GetFormattedText() => builder.ToString();
}

Most már hozzáadhat egy túlterhelést a(z) LogMessage-hoz a(z) Logger osztályban, hogy kipróbálhassa az új interpolált szövegkezelőt.

public void LogMessage(LogLevel level, LogInterpolatedStringHandler builder)
{
    if (EnabledLevel < level) return;
    Console.WriteLine(builder.GetFormattedText());
}

Nem kell eltávolítania az eredeti LogMessage metódust, a fordító az interpolált kezelőparamétert használó metódust részesíti előnyben egy string paraméterrel rendelkező metódussal szemben, ha az argumentum interpolált sztringkifejezés.

Az új kezelő meghívását a következő kód használatával ellenőrizheti fő programként:

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.");

Az alkalmazás futtatása az alábbi szöveghez hasonló kimenetet hoz létre:

        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.

A kimenet nyomon követése során láthatja, hogy a fordító hogyan ad hozzá kódot a kezelő meghívásához és a sztring létrehozásához:

  • A fordító egy hívást ad hozzá a kezelő létrehozására, amely során átadja a formátumsztringben szereplő literális szöveg teljes hosszát és a helyőrzők számát.
  • A fordító meghívja AppendLiteral és AppendFormatted a literális sztring minden szakaszához és minden helyőrzőhöz.
  • A fordító meghívja a LogMessage metódust, mint argumentumként a CoreInterpolatedStringHandler-et.

Végül vegyük észre, hogy az utolsó figyelmeztetés nem indítja el az interpolált sztringkezelőt. Az argumentum egy string, így a hívás meghívja a másik túlterhelést egy szöveges paraméterrel.

Fontos

Interpolált karakterlánc-kezelőkhöz csak akkor használjon ref struct-t, ha feltétlenül szükséges. A ref struct használatának korlátai lesznek, mivel azokat a veremen kell tárolni. Például nem fognak működni, ha egy interpolált karakterlánc-hely await kifejezést tartalmaz, mert a fordítónak a kezelőt a fordító által generált IAsyncStateMachine implementációban kell tárolnia.

További funkciók hozzáadása a kezelőhöz

Az interpolált sztringkezelő előző verziója implementálja a mintát. Az összes helyőrző kifejezés feldolgozásának elkerülése érdekében további információra van szüksége a kezelőben. Ebben a szakaszban fejlesztheti a kezelőt úgy, hogy kevesebb munkát kell végeznie, ha a létrehozott karakterlánc nincs beírva a naplóba. A System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute használatával leképezést adhat meg egy nyilvános API paraméterei és a kezelő konstruktorának paraméterei között. Ez biztosítja a kezelő számára a szükséges információkat annak megállapításához, hogy az interpolált sztringet ki kell-e értékelni.

Kezdjük a kezelő módosításaival. Először adjon hozzá egy mezőt, amely nyomon követi, hogy a kezelő engedélyezve van-e. Adjon hozzá két paramétert a konstruktorhoz: az egyik az üzenet naplószintjének megadásához, a másik pedig a naplóobjektumra mutató hivatkozás:

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}");
}

Ezután használja a mezőt úgy, hogy a kezelő csak a konstansokat vagy formázott objektumokat fűzze hozzá a végső sztring használatakor:

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");
}

Ezután frissítenie kell a LogMessage deklarációt, hogy a fordító átadja a további paramétereket a kezelő konstruktorának. Ezt a kezelő argumentum System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute kezeli:

public void LogMessage(LogLevel level, [InterpolatedStringHandlerArgument("", "level")] LogInterpolatedStringHandler builder)
{
    if (EnabledLevel < level) return;
    Console.WriteLine(builder.GetFormattedText());
}

Ez az attribútum megadja azon argumentumok listáját, amelyek a LogMessage-hoz illeszkednek, és a szükséges literalLength és formattedCount paramétereket követő paraméterekhez tartoznak. Az üres sztring ("") megadja a fogadót. A fordító a kezelő konstruktorának következő argumentumához a Logger által képviselt this objektum értékét helyettesíti. A fordító a level értékét a következő argumentumra cseréli. Tetszőleges számú argumentumot megadhat az Ön által írt kezelőkhöz. A hozzáadott argumentumok karakterlánc-argumentumok.

Ezt a verziót ugyanazzal a tesztkóddal futtathatja. Ezúttal a következő eredmények láthatók:

        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.

Láthatja, hogy a AppendLiteral és AppendFormat metódusok hívva vannak, de nem csinálnak semmit. A kezelő megállapította, hogy nincs szükség a végleges sztringre, ezért a kezelő nem hozza létre. Még mindig van néhány fejlesztés.

Először is hozzáadhat egy olyan AppendFormatted túlterhelést, amely az argumentumot egy olyan típusra korlátozza, amely System.IFormattable-et implementál. Ez a túlterhelés lehetővé teszi, hogy a hívók formázó karakterláncokat adjanak a helyőrzőkhöz. A módosítás végrehajtása során változtassuk meg a többi AppendFormatted és AppendLiteral metódus visszatérési típusát is void-ről bool -ra (ha ezen metódusok bármelyike eltérő visszatérési típussal rendelkezik, akkor fordítási hiba jelenik meg). Ez a módosítás lehetővé teszi a rövidzárlatozását. A metódusok false ad vissza, amely azt jelzi, hogy az interpolált sztringkifejezés feldolgozását le kell állítani. A true visszatérése azt jelzi, hogy folytatnia kell. Ebben a példában arra használja, hogy leállítja a feldolgozást, ha az eredményül kapott sztringre nincs szükség. A rövidzárolás részletesebb műveleteket támogat. A rögzített hosszúságú pufferek támogatásához leállíthatja a kifejezés feldolgozását egy bizonyos hossz elérése után. Vagy valamilyen feltétel azt jelezheti, hogy a fennmaradó elemekre nincs szükség.

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");
}

Ezzel a kiegészítéssel formátum karaktersorozatokat adhat meg az interpolált karaktersorozat kifejezésben.

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.");

Az első üzenet :t az aktuális idő "rövid időformátumát" adja meg. Az előző példában a kezelő számára létrehozható AppendFormatted metódus egyik túlterhelése látható. Nem kell általános argumentumot megadnia a formázott objektumhoz. A létrehozott típusok sztringgé alakításának hatékonyabb módjai lehetnek. Megírhatja a AppendFormatted olyan túlterheléseit, amelyek ezeknek a típusoknak a használatát fogadják el általános argumentum helyett. A fordító kiválasztja a legjobb túlterhelést. A futtatókörnyezet ezzel a technikával konvertálja a System.Span<T> sztringkimenetté. Egy egész szám paraméter hozzáadásával megadhatja a kimenet igazítását, a IFormattablejelenlétével vagy anélkül. A .NET 6-tal szállított System.Runtime.CompilerServices.DefaultInterpolatedStringHandler kilenc túlterhelést tartalmaz a AppendFormatted-ből különböző célokra. Referenciaként használhatja, amíg a céljainak megfelelő kezelőt készít.

Futtassa a mintát most, és láthatja, hogy a Trace üzenet esetén csak az első AppendLiteral kerül meghívásra:

        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.

Egyetlen végső frissítést végezhet a kezelő konstruktorán, amely javítja a hatékonyságot. A kezelő hozzáadhat egy végső out bool paramétert. Ha ezt a paramétert false értékre állítja, az azt jelzi, hogy a kezelőt egyáltalán nem kell meghívni az interpolált sztringkifejezés feldolgozásához:

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!;
}

Ez a módosítás azt jelenti, hogy eltávolíthatja a enabled mezőt. Ezután módosíthatja a AppendLiteral és AppendFormatted visszatérési típusát void-re. A minta futtatásakor a következő kimenet jelenik meg:

        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.

Az LogLevel.Trace megadásának egyetlen kimenete a konstruktor által biztosított kimenet. A kezelő jelezte, hogy nincs engedélyezve, ezért a Append metódusok egyike sem lett meghívva.

Ez a példa egy fontos pontot mutat be az interpolált sztringkezelők számára, különösen naplózási kódtárak használatakor. Előfordulhat, hogy a helyőrzőkben nem jelentkeznek mellékhatások. Adja hozzá a következő kódot a fő programhoz, és tekintse meg ezt a viselkedést működés közben:

int index = 0;
int numberOfIncrements = 0;
for (var level = LogLevel.Critical; level <= LogLevel.Trace; level++)
{
    Console.WriteLine(level);
    logger.LogMessage(level, $"{level}: Increment index a few times {index++}, {index++}, {index++}, {index++}, {index++}");
    numberOfIncrements += 5;
}
Console.WriteLine($"Value of index {index}, value of numberOfIncrements: {numberOfIncrements}");

Láthatja, hogy a index változó a ciklus minden iterációjának ötszörösére növekszik. Mivel a helyőrzők kiértékelése csak a Critical, Error és Warning szintekre történik, de Information és Traceesetében nem, a index végső értéke nem felel meg a várakozásoknak.

Critical
Critical: Increment index a few times 0, 1, 2, 3, 4
Error
Error: Increment index a few times 5, 6, 7, 8, 9
Warning
Warning: Increment index a few times 10, 11, 12, 13, 14
Information
Trace
Value of index 15, value of numberOfIncrements: 25

Az interpolált sztringkezelők nagyobb mértékben szabályozják az interpolált sztringkifejezések sztringgé alakításának módját. A .NET-futtatókörnyezeti csapat ezt a funkciót használta a teljesítmény több területen történő javítására. Ugyanezt a képességet használhatja a saját kódtáraiban is. A további felfedezéshez tekintse meg a System.Runtime.CompilerServices.DefaultInterpolatedStringHandler-t. Teljesebb megvalósítást biztosít, mint amit Ön itt készített. Sokkal több túlterhelést lát, amelyek a Append metódusokhoz lehetségesek.