Öğretici: Özel dize ilişkilendirme işleyicisi yazma
Bu öğreticide şunların nasıl yapılacağını öğreneceksiniz:
- Dize ilişkilendirme işleyicisi desenini uygulama
- Bir dize ilişkilendirme işleminde alıcıyla etkileşime geçin.
- Dize enterpolasyon işleyicisine bağımsız değişkenler ekleyin
- Dize ilişkilendirme için yeni kitaplık özelliklerini anlama
Önkoşullar
Makinenizi .NET çalıştıracak şekilde ayarlamanız gerekir. C# derleyicisi Visual Studio 2022 veya .NET SDKile kullanılabilir.
Bu öğreticide, Visual Studio veya .NET CLI dahil olmak üzere C# ve .NET hakkında bilgi sahibi olduğunuz varsayılır.
özel olarak ilişkilendirilmiş dize işleyicisi yazabilirsiniz. İnterpolasyonlu dize işleyicisi, bir interpolasyonlu dizedeki yer tutucu ifadeyi işleyen bir türdür. Özel bir işleyici olmadan, yer tutucular String.Format'a benzer biçimde işlenirler. Her yer tutucu metin olarak biçimlendirilir ve ardından bileşenler sonuçta elde edilen dizeyi oluşturacak şekilde birleştirilir.
Sonuçta elde edilen dize hakkındaki bilgileri kullandığınız herhangi bir senaryo için bir işleyici yazabilirsiniz. Kullanılacak mı? Biçimde hangi kısıtlamalar var? Bazı örnekler şunlardır:
- Sonuçta elde edilen dizelerin hiçbirinin 80 karakter gibi bir sınırdan büyük olmaması gerekebilir. Sabit uzunlukta bir arabelleği doldurmak için ilişkilendirilmiş dizeleri işleyebilir ve arabellek uzunluğuna ulaşıldıktan sonra işlemeyi durdurabilirsiniz.
- Tablosal bir biçiminiz olabilir ve her yer tutucu sabit uzunlukta olmalıdır. Özel işleyici, tüm istemci kodunu uyumlu olmaya zorlamak yerine bunu zorunlu kılabilir.
Bu öğreticide, temel performans senaryolarından biri için bir dize ilişkilendirme işleyicisi oluşturacaksınız: günlük kitaplıkları. Yapılandırılan günlük düzeyine bağlı olarak, günlük iletisi oluşturma işi gerekmeyebilir. Loglama kapalıysa, ara eklenmiş bir dize ifadesinden bir dize oluşturma çalışması gerekli değildir. Mesaj hiçbir zaman yazdırılmayacağı için herhangi bir dize birleştirme atlanabilir. Ayrıca, yığın izlemeleri üretme de dahil olmak üzere yer tutucularda kullanılan ifadelerin yapılmasına gerek yoktur.
Bir interpolasyonlu dize işleyicisi, biçimlendirilmiş dizenin kullanılıp kullanılmayacağını belirleyebilir ve yalnızca gerçekten gerekirse gerekli çalışmayı gerçekleştirebilir.
İlk uygulama
Farklı düzeyleri destekleyen temel bir Logger
sınıfından başlayalım:
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);
}
}
Bu Logger
altı farklı düzeyi destekler. Mesaj günlük seviyesi filtresini geçmezse çıktı olmaz. Günlükçü için genel API, ileti olarak bir (tam olarak biçimlendirilmiş) dize kabul eder. String'i oluşturmak için tüm çalışmalar zaten yapılmıştır.
İşleyici desenini uygulama
Bu adım, geçerli davranışı yeniden oluşturan ilişkilendirilmiş dize işleyicisi oluşturmaktır. Gömülü dize işleyicisi, aşağıdaki özelliklere sahip olması gereken bir türdür:
- Türe uygulanan System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute.
-
literalLength
veformattedCount
olmak üzere ikiint
parametresi olan bir oluşturucu. (Daha fazla parametreye izin verilir). - Açık bir
AppendLiteral
metodu, imzası:public void AppendLiteral(string s)
. - İmzası olan genel bir genel
AppendFormatted
yöntemi:public void AppendFormatted<T>(T t)
.
Oluşturucu, biçimlendirilmiş dizeyi dahili olarak oluşturur ve bir istemcinin bu dizeyi alması için bir üye sağlar. Aşağıdaki kod, bu gereksinimleri karşılayan bir LogInterpolatedStringHandler
türünü gösterir:
[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();
}
Artık yeni interpolasyonlu dize işleyicinizi denemek için Logger
sınıfındaki LogMessage
overload'ını ekleyebilirsiniz:
public void LogMessage(LogLevel level, LogInterpolatedStringHandler builder)
{
if (EnabledLevel < level) return;
Console.WriteLine(builder.GetFormattedText());
}
Özgün LogMessage
yöntemini kaldırmanız gerekmez; derleyici, bağımsız değişken ilişkilendirilmiş bir dize ifadesi olduğunda string
parametresine sahip bir yöntem yerine ilişkilendirilmiş işleyici parametresine sahip bir yöntemi tercih eder.
Yeni işleyicinin ana program olarak aşağıdaki kodu kullanarak çağrıldığı doğrulayabilirsiniz:
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.");
Uygulamanın çalıştırılması aşağıdaki metne benzer bir çıkış oluşturur:
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.
Çıktıyı izlediğinizde, derleyicinin işleyiciyi çağırmak ve dizeyi derlemek için nasıl kod eklediğini görebilirsiniz:
- Derleyici, biçim dizesindeki değişmez metnin toplam uzunluğunu ve yer tutucu sayısını geçirerek işleyiciyi oluşturmak için bir çağrı ekler.
- Derleyici, literal dizgenin her bölümü ve her yer tutucu için
AppendLiteral
veAppendFormatted
çağrıları ekler. - Derleyici, bağımsız değişken olarak
CoreInterpolatedStringHandler
'i kullanarakLogMessage
yöntemini çağırır.
Son olarak, son uyarının ilişkilendirilmiş dize işleyicisini çağırmadığını fark edin. Bağımsız değişken bir string
olduğundan, bu çağrı bir dize parametresiyle diğer aşırı yüklemeyi tetikler.
Önemli
yalnızca kesinlikle gerekliyse, ilişkilendirilmiş dize işleyicileri için ref struct
kullanın.
ref struct
kullanmanın yığında depolanması gerektiğinden dolayı bazı sınırlamaları vardır. Örneğin, interpolasyonlu bir dize deliği await
ifadesi içeriyorsa, derleyicinin işleyiciyi derleyici tarafından oluşturulan IAsyncStateMachine
uygulamasında depolaması gerektiğinden çalışmazlar.
İşleyiciye daha fazla özellik ekleme
Interpolasyonlu dize işleyicisinin önceki sürümü deseni uygular. Her yer tutucu ifadenin işlenmesini önlemek için işleyicide daha fazla bilgiye ihtiyacınız vardır. Bu bölümde, işleyicinizi geliştirerek, oluşturduğunuz dize günlüğe yazılmadığında daha az çalışmasını sağlayın. System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute kullanarak genel API'ye parametreler ve işleyicinin oluşturucusuna parametreler arasında eşleme yaparsınız. Bu, işleyiciye, ilişkilendirilmiş dizenin değerlendirilmesinin gerekip gerekmediğini belirlemek için gereken bilgileri sağlar.
İşleyici'de yapılan değişikliklerle başlayalım. İlk olarak, işleyicinin etkin olup olmadığını izlemek için bir alan ekleyin. Oluşturucuya iki parametre ekleyin: biri, bu iletinin günlük düzeyini belirtmek için; diğeri ise günlük nesnesine referans olarak.
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}");
}
Ardından, işleyicinizin yalnızca son dize kullanılacağı zaman sabit değerleri veya biçimlendirilmiş nesneleri eklemesi için alanı böylece kullanın:
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");
}
Ardından, derleyicinin ek parametreleri işleyicinin oluşturucusna geçirmesi için LogMessage
bildirimini güncelleştirmeniz gerekir. İşleyici bağımsız değişkeninde System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute kullanılarak işlenir:
public void LogMessage(LogLevel level, [InterpolatedStringHandlerArgument("", "level")] LogInterpolatedStringHandler builder)
{
if (EnabledLevel < level) return;
Console.WriteLine(builder.GetFormattedText());
}
Bu öznitelik, gerekli literalLength
ve formattedCount
parametrelerinden sonraki parametrelere uygun LogMessage
için bağımsız değişkenlerin listesini belirtir. Boş dize (""), alıcıyı belirtir. Derleyici, işleyici oluşturucusunun sonraki bağımsız değişkenine this
tarafından temsil edilen Logger
nesnesinin değerini atar. Derleyici, aşağıdaki bağımsız değişken için level
değerini kullanır. Yazdığınız herhangi bir işleyici için istediğiniz sayıda bağımsız değişken sağlayabilirsiniz. Eklediğiniz bağımsız değişkenler dize bağımsız değişkenleridir.
Bu sürümü aynı test kodunu kullanarak çalıştırabilirsiniz. Bu kez aşağıdaki sonuçları görürsünüz:
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.
AppendLiteral
ve AppendFormat
yöntemlerinin çağrıldığını ancak herhangi bir iş yapmadıklarını görebilirsiniz. İşleyici, son dizenin gerekli olmadığını belirledi, bu nedenle işleyici bunu derlemez. Yine de yapılması gereken birkaç geliştirme vardır.
İlk olarak, bağımsız değişkeni System.IFormattableuygulayan bir türle kısıtlayan bir AppendFormatted
aşırı yüklemesi ekleyebilirsiniz. Bu aşırı yükleme, çağıranların yer tutuculara biçim dizeleri eklemesini sağlar. Bu değişikliği yaparken, diğer AppendFormatted
ve AppendLiteral
yöntemlerinin dönüş türünü de değiştirelim; void
bool
(bu yöntemlerden herhangi biri farklı dönüş türlerine sahipse, derleme hatası alırsınız). Bu değişiklik içinkısa devreyi etkinleştirir. Yöntemler, ilişkilendirilmiş dize ifadesinin işlenmesinin durdurulması gerektiğini belirtmek için false
döndürür.
true
döndürülmesi, devam etmesi gerektiğini gösterir. Bu örnekte, sonuçta elde edilen dize gerekli olmadığında işlemeyi durdurmak için bunu kullanıyorsunuz. Kısa devre daha ayrıntılı eylemleri destekler. Belirli bir uzunluğa ulaştıklarında ifadelerin işlenmesini durdurarak sabit uzunlukta arabellekleri destekleyebilirsiniz. Veya bazı koşullar kalan öğelerin gerekli olmadığını gösterebilir.
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");
}
Bu eklemeyle, ilişkilendirilmiş dize ifadenizde biçim dizeleri belirtebilirsiniz:
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.");
İlk iletideki :t
geçerli saat için "kısa saat biçimini" belirtir. Önceki örnekte, işleyiciniz için oluşturabileceğiniz AppendFormatted
yöntemine yönelik aşırı yüklemelerden biri gösterildi. Biçimlendirilen nesne için genel bir bağımsız değişken belirtmeniz gerekmez. Oluşturduğunuz türleri dizeye dönüştürmek için daha verimli yöntemler bulunabilir. Genel bağımsız değişken yerine bu türleri alan AppendFormatted
aşırı yüklemeleri yazabilirsiniz. Derleyici en iyi aşırı yüklemeyi seçer. Çalışma zamanı, System.Span<T>'ı metin çıktısına dönüştürmek için bu tekniği kullanır.
IFormattableile veya olmadan çıktının hizalama belirtmek için bir tamsayı parametresi ekleyebilirsiniz. .NET 6 ile birlikte gelen System.Runtime.CompilerServices.DefaultInterpolatedStringHandler, farklı kullanımlar için dokuz AppendFormatted aşırı yüklemesi içerir. Bunu, amaçlarınıza uygun bir işleyici oluştururken başvuru olarak kullanabilirsiniz.
Örneği şimdi çalıştırdığınızda Trace
iletisi için yalnızca ilk AppendLiteral
çağrıldığını görürsünüz:
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.
İşleyicinin oluşturucusunun verimliliğini artıran son bir güncelleştirme yapabilirsiniz. İşleyici son bir out bool
parametresi ekleyebilir. Bu parametreyi false
olarak ayarlamak, ilişkilendirilmiş dize ifadesini işlemek için işleyicinin hiç çağrılmaması gerektiğini gösterir:
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!;
}
Bu değişiklik, enabled
alanını kaldırabileceğiniz anlamına gelir. Ardından, AppendLiteral
ve AppendFormatted
dönüş türünü void
olarak değiştirebilirsiniz.
Şimdi örneği çalıştırdığınızda aşağıdaki çıkışı görürsünüz:
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.
LogLevel.Trace
belirtildiğinde tek çıkış oluşturucudan gelen çıkıştır. İşleyici etkinleştirilmediğini belirttiğinden, Append
yöntemlerinden hiçbiri çağrılmamıştı.
Bu örnekte, özellikle günlük kitaplıkları kullanılırken, ilişkilendirilmiş dize işleyicileri için önemli bir nokta gösterilmektedir. Yer tutuculardaki yan etkiler oluşmayabilir. Ana programınıza aşağıdaki kodu ekleyin ve bu davranışı çalışırken görün:
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}");
index
değişkeninin döngünün her yinelemesinin beş kat artırıldığından görebilirsiniz. Yer tutucular yalnızca Critical
, Error
ve Warning
düzeyleri için değerlendirildiğinden, Information
ve Trace
için değerlendirilmediği için, index
'in son değeri beklentiyle uyuşmaz.
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
İlişkili dize işleyicileri, ilişkilendirilmiş dize ifadesinin bir dizeye nasıl dönüştürüldüğü üzerinde daha fazla denetim sağlar. .NET çalışma zamanı ekibi bu özelliği çeşitli alanlarda performansı geliştirmek için kullandı. Kendi kitaplıklarınızda aynı özelliği kullanabilirsiniz. Daha fazla araştırmak için System.Runtime.CompilerServices.DefaultInterpolatedStringHandlerbakın. Burada oluşturduğunuzdan daha eksiksiz bir uygulama sağlar.
Append
yöntemleri için mümkün olan çok daha fazla aşırı yükleme görürsünüz.