{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"},{"name":"Jendrik Brack","url":"https://daily-devops.net/authors/jendrik/"}],"description":"Recent content in Hidden Gems in Software Development on Daily DevOps \u0026 .NET","favicon":"https://daily-devops.net/images/logo_hu_6465d873dfa490cf.png","feed_url":"https://daily-devops.net/tags/hidden-gems/feed.json","home_page_url":"https://daily-devops.net/tags/hidden-gems/","icon":"https://daily-devops.net/images/logo_hu_5926de77762241ba.png","items":[{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eString formatting is everywhere in .NET applications: logging, debugging, user messages, dynamic content. Methods like \u003ca href=\"https://learn.microsoft.com/en-us/dotnet/api/system.string.format\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003e\u003ccode\u003estring.Format\u003c/code\u003e\u003c/a\u003e and interpolated strings are convenient, but they have a cost: \u003cstrong\u003eparsing overhead\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eEvery time you call \u003ccode\u003estring.Format()\u003c/code\u003e, the runtime parses that format string to understand its structure, find placeholders, and figure out how to substitute values. When you use the same format string repeatedly (loops, logging, request handling), this parsing is pure waste. You\u0026rsquo;re doing the same work over and over.\u003c/p\u003e\n\u003cp\u003eEnter \u003ca href=\"https://learn.microsoft.com/en-us/dotnet/api/system.text.compositeformat\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003e\u003ccode\u003eCompositeFormat\u003c/code\u003e\u003c/a\u003e, introduced in .NET 8. Parse a format string (\u003cstrong\u003esee\u003c/strong\u003e: \u003ca href=\"https://learn.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eComposite formatting\u003c/a\u003e) \u003cstrong\u003eonce\u003c/strong\u003e, reuse it many times. No more repeated parsing, better performance. Simple concept, real impact.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-problem-repeated-parsing-overhead\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#the-problem-repeated-parsing-overhead\" title=\"The Problem: Repeated Parsing Overhead\"\u003eThe Problem: Repeated Parsing Overhead\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eConsider a typical logging scenario:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"m\"\u003e10000\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e++)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003emessage\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Processing item {0} of {1}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"m\"\u003e10000\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Log or use the message\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn this example, the format string \u003ccode\u003e\u0026quot;Processing item {0} of {1}\u0026quot;\u003c/code\u003e gets parsed 10,000 times. Same string, 10,000 parses. Each parse scans for placeholders, extracts format specifiers, validates structure, builds an internal representation. In a high-throughput app (web server, batch processor, real-time system), this adds up fast.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-solution-compositeformat\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#the-solution-compositeformat\" title=\"The Solution: CompositeFormat\"\u003eThe Solution: CompositeFormat\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eCompositeFormat\u003c/code\u003e separates parsing from formatting:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Parse the format string once\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eCompositeFormat\u003c/span\u003e \u003cspan class=\"n\"\u003eformat\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eCompositeFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eParse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Processing item {0} of {1}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"m\"\u003e10000\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e++)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Reuse the parsed format many times\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003emessage\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eformat\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"m\"\u003e10000\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Log or use the message\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eParse once, reuse the \u003ccode\u003eCompositeFormat\u003c/code\u003e instance. You just cut out 9,999 redundant operations.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"how-compositeformat-works\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#how-compositeformat-works\" title=\"How CompositeFormat Works\"\u003eHow CompositeFormat Works\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe internals are straightforward. \u003ccode\u003eCompositeFormat\u003c/code\u003e uses the same parsing logic as \u003ccode\u003estring.Format()\u003c/code\u003e, but stores the result for reuse.\u003c/p\u003e\n\u003cp\u003eWhen you call \u003ccode\u003eCompositeFormat.Parse(string)\u003c/code\u003e, the runtime scans the format string, validates it, and builds an internal representation (literal text + placeholders). That\u0026rsquo;s it, done once. When you call \u003ccode\u003estring.Format(IFormatProvider, CompositeFormat, ...)\u003c/code\u003e, the runtime skips parsing entirely and just substitutes values.\u003c/p\u003e\n\u003cp\u003eThe \u003ccode\u003eCompositeFormat\u003c/code\u003e instance is immutable and thread-safe, so you can reuse it anywhere, even across threads. Classic .NET philosophy: if you\u0026rsquo;re doing the same thing repeatedly, don\u0026rsquo;t pay the cost every time.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"performance-benchmarks-real-world-impact\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#performance-benchmarks-real-world-impact\" title=\"Performance Benchmarks: Real-World Impact\"\u003ePerformance Benchmarks: Real-World Impact\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eBenchmarks from the .NET runtime team show 15-30% reduction in execution time for repeated formatting operations, fewer allocations, less GC pressure, higher throughput in logging-heavy workloads.\u003c/p\u003e\n\u003cp\u003eThese gains matter in high-frequency scenarios: logging frameworks processing thousands of messages per second, request handlers, batch processing, telemetry systems.\u003c/p\u003e\n\u003cp\u003eTake a web API handling 50,000 requests per minute. Reduce formatting overhead by 20%, and you might handle 10,000 more requests on the same hardware. Lower CPU usage, lower latency. That\u0026rsquo;s real money saved.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"when-to-use-compositeformat\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#when-to-use-compositeformat\" title=\"When to Use CompositeFormat\"\u003eWhen to Use CompositeFormat\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eUse \u003ccode\u003eCompositeFormat\u003c/code\u003e when the same format string gets used repeatedly: loops, hot paths, frequently called methods. It makes sense when performance matters (CPU-bound operations, latency reduction, high-throughput systems) and when you control the format string at compile time.\u003c/p\u003e\n\u003cp\u003eHigh-frequency logging is perfect for this. Parse once, reuse across thousands of log calls. Request/response handling, batch processing, performance-critical libraries—all good candidates.\u003c/p\u003e\n\u003cp\u003eDon\u0026rsquo;t use it for one-off formatting. Creating the \u003ccode\u003eCompositeFormat\u003c/code\u003e instance costs more than you save. Skip it for dynamic format strings that change at runtime. And for simple interpolated strings, just use \u003ccode\u003e$\u0026quot;...\u0026quot;\u003c/code\u003e. Readability matters.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"usage-examples\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#usage-examples\" title=\"Usage Examples\"\u003eUsage Examples\u003c/a\u003e\u003c/h2\u003e\n\n\n\n\n\u003ch3 id=\"basic-pattern\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#basic-pattern\" title=\"Basic Pattern\"\u003eBasic Pattern\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eParse your format string once with \u003ccode\u003eCompositeFormat.Parse()\u003c/code\u003e, store it as \u003ccode\u003estatic readonly\u003c/code\u003e, reuse it:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Parse format string once\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003ereadonly\u003c/span\u003e \u003cspan class=\"n\"\u003eCompositeFormat\u003c/span\u003e \u003cspan class=\"n\"\u003eLogFormat\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eCompositeFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eParse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;User {0} logged in at {1:yyyy-MM-dd HH:mm:ss}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Reuse many times\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003euserId\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003euserId\u003c/span\u003e \u003cspan class=\"p\"\u003e\u0026lt;=\u003c/span\u003e \u003cspan class=\"m\"\u003e1000\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"n\"\u003euserId\u003c/span\u003e\u003cspan class=\"p\"\u003e++)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003emessage\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eLogFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003euserId\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eDateTime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eNow\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emessage\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\n\n\n\u003ch3 id=\"integration-pattern\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#integration-pattern\" title=\"Integration Pattern\"\u003eIntegration Pattern\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eFor larger apps, put all your format templates in one place:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eMessageFormats\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003ereadonly\u003c/span\u003e \u003cspan class=\"n\"\u003eCompositeFormat\u003c/span\u003e \u003cspan class=\"n\"\u003eErrorFormat\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eCompositeFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eParse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Error: {0} occurred at {1}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003ereadonly\u003c/span\u003e \u003cspan class=\"n\"\u003eCompositeFormat\u003c/span\u003e \u003cspan class=\"n\"\u003eSuccessFormat\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eCompositeFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eParse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Success: Operation {0} completed with result {1}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Usage across your application\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003eerrorMessage\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eMessageFormats\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eErrorFormat\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eexception\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eMessage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eDateTime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eUtcNow\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWorks well with dependency injection, keeps formatting consistent across your app.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"integration-with-existing-code\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#integration-with-existing-code\" title=\"Integration with Existing Code\"\u003eIntegration with Existing Code\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eYou don\u0026rsquo;t need to rewrite everything. Profile your code (dotTrace, \u003ca href=\"https://github.com/microsoft/perfview\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003ePerfView\u003c/a\u003e), find the hot format strings, extract them to \u003ccode\u003estatic readonly\u003c/code\u003e fields, swap the method calls. Benchmark before and after.\u003c/p\u003e\n\u003cp\u003eMigration is usually just extracting a string literal and changing a method call. Small change, real impact.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"best-practices\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#best-practices\" title=\"Best Practices\"\u003eBest Practices\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eCache instances as \u003ccode\u003estatic readonly\u003c/code\u003e fields. Focus on hot paths: loops, high-frequency methods, performance-critical code. Benchmark with BenchmarkDotNet. Keep format strings simple. Thread-safe by default. Combine with \u003ccode\u003eStringBuilder\u003c/code\u003e when building complex strings.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-bigger-picture\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#the-bigger-picture\" title=\"The Bigger Picture\"\u003eThe Bigger Picture\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eCompositeFormat\u003c/code\u003e fits into .NET\u0026rsquo;s broader push for zero-cost abstractions. \u003ccode\u003eSpan\u0026lt;T\u0026gt;\u003c/code\u003e and \u003ccode\u003eMemory\u0026lt;T\u0026gt;\u003c/code\u003e for zero-allocation slicing. \u003ccode\u003eArrayPool\u0026lt;T\u0026gt;\u003c/code\u003e for object pooling. \u003ccode\u003eValueTask\u0026lt;T\u0026gt;\u003c/code\u003e for allocation-free async. Source generators for compile-time code generation. Native AOT for faster startup.\u003c/p\u003e\n\u003cp\u003eThe pattern is consistent: control over performance without sacrificing usability. Opt-in when you need it, invisible when you don\u0026rsquo;t.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"evolution-across-net-versions\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#evolution-across-net-versions\" title=\"Evolution Across .NET Versions\"\u003eEvolution Across .NET Versions\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eCompositeFormat\u003c/code\u003e landed in .NET 8. Each release since has made it better.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e.NET 9\u003c/strong\u003e optimized internals. Same API, faster formatting engine. Fewer allocations, especially with many placeholders. Less GC pressure.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e.NET 10\u003c/strong\u003e improved JIT compiler understanding. More aggressive inlining for repeated formatting. Better interop with \u003ccode\u003eSpan\u0026lt;char\u0026gt;\u003c/code\u003e and \u003ccode\u003eMemory\u0026lt;char\u0026gt;\u003c/code\u003e for allocation-free scenarios.\u003c/p\u003e\n\u003cp\u003eUpgrading from .NET 6 or 7 to .NET 8+ gets you \u003ccode\u003eCompositeFormat\u003c/code\u003e plus a faster runtime overall.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"conclusion\"\u003e\u003ca href=\"/posts/compositeformat-performance-boost/#conclusion\" title=\"Conclusion\"\u003eConclusion\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eCompositeFormat\u003c/code\u003e is small but effective. Parse once, format many times. Less CPU, fewer allocations, better throughput.\u003c/p\u003e\n\u003cp\u003eThe gains are real: logging, request handling, batch processing all benefit. It\u0026rsquo;s opt-in, so adopt it incrementally without breaking existing code.\u003c/p\u003e\n\u003cp\u003eProfile your hot paths, find repeated formatting, switch to \u003ccode\u003eCompositeFormat\u003c/code\u003e.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSimple change, \u003cstrong\u003emeasurable results.\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2025-10-23T17:00:00+02:00","id":"https://daily-devops.net/posts/compositeformat-performance-boost/","language":"en","summary":"Parse once, format a thousand times. CompositeFormat eliminates redundant parsing overhead and makes your .NET apps faster with one simple change.","tags":["performance","dotnet","csharp","bestpractices","hidden-gems","softwareengineering"],"title":"Stop Parsing the Same String Twice: CompositeFormat in .NET","url":"https://daily-devops.net/posts/compositeformat-performance-boost/"},{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eYour string operations are killing your API. You just haven\u0026rsquo;t measured it yet.\u003c/p\u003e\n\u003cp\u003eWhile you\u0026rsquo;re busy optimizing database queries and adding cache layers, thousands of string searches per second are quietly eating your CPU budget. The problem isn\u0026rsquo;t visible in your APM dashboard because it\u0026rsquo;s distributed across every request. But it\u0026rsquo;s there. Compounding. Scaling linearly with load.\u003c/p\u003e\n\u003cp\u003eI discovered this the hard way when a log processing API started choking under production traffic. The bottleneck? String validation and sanitization. The fix? A .NET 8 feature that delivered a \u003cstrong\u003e5x performance improvement\u003c/strong\u003e and let us shut down servers instead of adding them. And it\u0026rsquo;s gotten even better in .NET 9 and 10.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e isn\u0026rsquo;t a nice-to-have optimization. It\u0026rsquo;s the difference between infrastructure costs that scale with your success versus infrastructure costs that scale with your inefficiency.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-real-problem-string-operations-dont-scale-like-you-think\"\u003e\u003ca href=\"/posts/searchvalues-saved-us-from-scaling-hell/#the-real-problem-string-operations-dont-scale-like-you-think\" title=\"The Real Problem: String Operations Don\u0026rsquo;t Scale Like You Think\"\u003eThe Real Problem: String Operations Don\u0026rsquo;t Scale Like You Think\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s what nobody tells you about string operations: they scale linearly with load. Double the traffic? Double the CPU usage. Add more features that parse strings? Multiply the pain.\u003c/p\u003e\n\u003cp\u003eTraditional approaches using \u003ccode\u003estring.IndexOfAny()\u003c/code\u003e or custom loops work fine when you\u0026rsquo;re processing dozens of requests per second. They fail silently when you\u0026rsquo;re processing thousands. No exceptions. No errors. Just slow, expensive CPU burn that compounds into massive infrastructure waste.\u003c/p\u003e\n\u003cp\u003eConsider a real scenario: a log aggregation service processing application logs in real-time. Each log entry needs sanitization, sensitive data detection, and validation before storage. That\u0026rsquo;s multiple string operations per log entry. Thousands of log entries per second. Millions of string operations per minute.\u003c/p\u003e\n\u003cp\u003eWith traditional methods, you\u0026rsquo;re leaving performance on the table. Modern CPUs have SIMD instructions that can process multiple characters simultaneously, but \u003ccode\u003eIndexOfAny()\u003c/code\u003e doesn\u0026rsquo;t use them consistently. It\u0026rsquo;s a generic solution for a problem that benefits from specialization.\u003c/p\u003e\n\u003cp\u003eThat\u0026rsquo;s where \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e comes in. It\u0026rsquo;s a frozen, immutable set of values that .NET analyzes once at creation time, then optimizes specifically for your search pattern using the fastest algorithm available, whether that\u0026rsquo;s SIMD vectorization, bitmap lookups, or other strategies depending on your value set. The difference isn\u0026rsquo;t marginal. It\u0026rsquo;s transformational.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"what-makes-searchvaluest-different\"\u003e\u003ca href=\"/posts/searchvalues-saved-us-from-scaling-hell/#what-makes-searchvaluest-different\" title=\"What Makes SearchValues\u0026lt;T\u0026gt; Different?\"\u003eWhat Makes \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e Different?\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e was introduced in .NET 8. Most developers still haven\u0026rsquo;t heard of it. That\u0026rsquo;s a mistake that\u0026rsquo;s costing infrastructure budget.\u003c/p\u003e\n\u003cp\u003eWhen you create a \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e instance, .NET analyzes your value set \u003cstrong\u003eonce\u003c/strong\u003e and selects the most efficient search algorithm. SIMD vectorization when possible. Bitmap lookups for dense character sets. Optimized branching for sparse sets. The runtime chooses. You don\u0026rsquo;t.\u003c/p\u003e\n\u003cp\u003eYou pay the analysis cost once at startup. Then you reuse that optimized instance across millions of operations. That\u0026rsquo;s the trade-off: slightly more expensive creation, dramatically cheaper execution.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eTLDR; Pure performance win.\u003c/p\u003e\n\u003c/blockquote\u003e\n\n\n\n\n\u003ch2 id=\"net-9-and-net-10-getting-even-faster\"\u003e\u003ca href=\"/posts/searchvalues-saved-us-from-scaling-hell/#net-9-and-net-10-getting-even-faster\" title=\".NET 9 and .NET 10: Getting Even Faster\"\u003e.NET 9 and .NET 10: Getting Even Faster\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e didn\u0026rsquo;t stop at .NET 8. Microsoft kept pushing performance further with each release, expanding both capabilities and raw speed.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"net-9-multi-substring-search\"\u003e\u003ca href=\"/posts/searchvalues-saved-us-from-scaling-hell/#net-9-multi-substring-search\" title=\".NET 9: Multi-Substring Search\"\u003e.NET 9: Multi-Substring Search\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003e.NET 8 gave us \u003ccode\u003eSearchValues\u0026lt;char\u0026gt;\u003c/code\u003e and \u003ccode\u003eSearchValues\u0026lt;byte\u0026gt;\u003c/code\u003e for character-level searches. .NET 9 expands this with \u003ccode\u003eSearchValues\u0026lt;string\u0026gt;\u003c/code\u003e for searching multiple substrings within a string.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eBefore (Regex):\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Looking for \u0026#34;error\u0026#34;, \u0026#34;warning\u0026#34;, or \u0026#34;critical\u0026#34; (case-insensitive)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eregex\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eRegex\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;(?i)error|warning|critical\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eRegexOptions\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCompiled\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003ebool\u003c/span\u003e \u003cspan class=\"n\"\u003efound\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eregex\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eIsMatch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elogMessage\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003eAfter (SearchValues):\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003ereadonly\u003c/span\u003e \u003cspan class=\"n\"\u003eSearchValues\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eLogKeywords\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eSearchValues\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCreate\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;error\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;warning\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;critical\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003eStringComparison\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eOrdinalIgnoreCase\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003ebool\u003c/span\u003e \u003cspan class=\"n\"\u003efound\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elogMessage\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAsSpan\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"n\"\u003eContainsAny\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eLogKeywords\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe Regex compiler in .NET 9 \u003cstrong\u003euses this automatically\u003c/strong\u003e when it detects multi-substring patterns. Call it directly to skip regex parsing overhead entirely.\u003c/p\u003e\n\u003cp\u003eMulti-substring searches in log analysis became 3-4x faster in .NET 9. Patterns that were \u0026ldquo;too expensive\u0026rdquo; for every log entry suddenly became viable for real-time alerting.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eTLDR; .NET 9 is even faster for multi-substring searches.\u003c/p\u003e\n\u003c/blockquote\u003e\n\n\n\n\n\u003ch3 id=\"net-10-hardware-level-optimizations\"\u003e\u003ca href=\"/posts/searchvalues-saved-us-from-scaling-hell/#net-10-hardware-level-optimizations\" title=\".NET 10: Hardware-Level Optimizations\"\u003e.NET 10: Hardware-Level Optimizations\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003e.NET 10 takes the optimization further by targeting CPU instruction sets directly. On AVX-512, it replaces two instructions with a single \u003ccode\u003ePermuteVar64x8x2\u003c/code\u003e, cutting CPU cycles in half. ARM64 gets cheaper \u003ccode\u003eUnzipEven\u003c/code\u003e instructions, delivering better performance on AWS Graviton and Azure Ampere instances. Case-insensitive searches benefit from extended fast-path logic that reduces validation overhead.\u003c/p\u003e\n\u003cp\u003eIf you\u0026rsquo;re running .NET 9 and seeing good results, .NET 10 makes the same operations \u003cstrong\u003e10-20% faster\u003c/strong\u003e without code changes. Just upgrade the runtime.\u003c/p\u003e\n\u003cp\u003eFor heavy text processing workloads, that 10-20% compounds across millions of operations. It\u0026rsquo;s the difference between needing an extra worker node versus staying within capacity.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eTLDR; Once again, .NET 10 is faster.\u003c/p\u003e\n\u003c/blockquote\u003e\n\n\n\n\n\u003ch2 id=\"production-numbers\"\u003e\u003ca href=\"/posts/searchvalues-saved-us-from-scaling-hell/#production-numbers\" title=\"Production Numbers\"\u003eProduction Numbers\u003c/a\u003e\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eBenchmarks lie. Production doesn\u0026rsquo;t.\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIn a real log processing pipeline handling production traffic, 10,000 log entries that previously took ~450ms now complete in ~85ms. That\u0026rsquo;s \u003cstrong\u003e5.3x faster on the same hardware\u003c/strong\u003e. Not \u003cem\u003e\u003cstrong\u003e20% faster\u003c/strong\u003e\u003c/em\u003e optimization theater. That\u0026rsquo;s handling five times more throughput without adding a single server. Infrastructure costs going down while traffic going up.\u003c/p\u003e\n\u003cp\u003eThis isn\u0026rsquo;t theoretical performance gains on a conference slide deck. This is the difference between scaling horizontally by adding servers versus scaling efficiently by using what you have. Between infrastructure costs that increase with growth versus costs that stay flat. Between firefighting capacity problems versus preventing them.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe business impact is direct: \u003cstrong\u003efewer servers, lower costs, same (or better) performance\u003c/strong\u003e.\u003c/p\u003e\n\u003c/blockquote\u003e\n\n\n\n\n\u003ch2 id=\"log-sanitization-without-the-performance-tax\"\u003e\u003ca href=\"/posts/searchvalues-saved-us-from-scaling-hell/#log-sanitization-without-the-performance-tax\" title=\"Log Sanitization Without the Performance Tax\"\u003eLog Sanitization Without the Performance Tax\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eLogging should be cheap and invisible. Write to stdout, let your log shipper handle it, done. Reality is messier.\u003c/p\u003e\n\u003cp\u003eLog sanitization is expensive. Stripping sensitive data and control characters before storage becomes a bottleneck at scale. And every production system does it because compliance demands it. Regulations like GDPR, PCI-DSS, and HIPAA all require sanitizing logs.\u003c/p\u003e\n\u003cbr /\u003e\n\u003cp\u003eHere\u0026rsquo;s a simplified example of log sanitization that removes dangerous characters and redacts sensitive data patterns:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Buffers\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Text\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eLogSanitizer\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Characters that could indicate injection attempts or corrupt data\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003ereadonly\u003c/span\u003e \u003cspan class=\"n\"\u003eSearchValues\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eDangerousCharacters\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eSearchValues\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCreate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x0B\\x0C\\x0E\\x0F\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                           \u003cspan class=\"s\"\u003e\u0026#34;\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1A\\x1B\\x1C\\x1D\\x1E\\x1F\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Patterns that often precede sensitive data in logs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003ereadonly\u003c/span\u003e \u003cspan class=\"n\"\u003eSearchValues\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eSensitiveMarkers\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eSearchValues\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCreate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;=:@\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003eSanitizeLogEntry\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003elogEntry\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eReadOnlySpan\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003espan\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elogEntry\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAsSpan\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// Fast path: if no dangerous characters, return original\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(!\u003c/span\u003e\u003cspan class=\"n\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eContainsAny\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eDangerousCharacters\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eProcessSensitiveData\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elogEntry\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// Slow path: rebuild without dangerous characters\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStringBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elogEntry\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLength\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(!\u003c/span\u003e\u003cspan class=\"n\"\u003eDangerousCharacters\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eContains\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAppend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eProcessSensitiveData\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eToString\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003eProcessSensitiveData\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// Find patterns like \u0026#34;password=\u0026#34;, \u0026#34;token:\u0026#34;, \u0026#34;apiKey@\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eReadOnlySpan\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003echar\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003espan\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAsSpan\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003eint\u003c/span\u003e \u003cspan class=\"n\"\u003emarkerIndex\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eIndexOfAny\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSensitiveMarkers\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emarkerIndex\u003c/span\u003e \u003cspan class=\"p\"\u003e==\u003c/span\u003e \u003cspan class=\"p\"\u003e-\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e// Redact the next 20 characters after sensitive markers\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStringBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLength\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAppend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003espan\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSlice\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emarkerIndex\u003c/span\u003e \u003cspan class=\"p\"\u003e+\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAppend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;[REDACTED]\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eToString\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003eThe Impact:\u003c/strong\u003e 87% reduction in sanitization time. Not in a benchmark. In production. Under real load.\u003c/p\u003e\n\u003cp\u003eThe fast path (clean logs) allocates \u003cstrong\u003ezero heap memory\u003c/strong\u003e. Garbage collector stays asleep. Sub-millisecond performance for 10KB log entries is the norm.\u003c/p\u003e\n\u003cp\u003eAcross millions of logs daily, that\u0026rsquo;s measurable infrastructure savings. We \u003cstrong\u003eshut down log processing workers\u003c/strong\u003e instead of adding them. Real money saved.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"what-you-need-to-know\"\u003e\u003ca href=\"/posts/searchvalues-saved-us-from-scaling-hell/#what-you-need-to-know\" title=\"What You Need to Know\"\u003eWhat You Need to Know\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eMicrosoft\u0026rsquo;s \u003ca href=\"https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1870\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eCA1870 analyzer\u003c/a\u003e throws warnings when it detects string searching that could use \u003ca href=\"https://learn.microsoft.com/en-us/dotnet/api/system.buffers.searchvalues-1\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003e\u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e\u003c/a\u003e. The warning appears on code like \u003ccode\u003etext.IndexOfAny(new[] { ',', ';', '|' })\u003c/code\u003e and suggests converting to \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e. The fix is straightforward: create a static readonly \u003ccode\u003eSearchValues\u0026lt;char\u0026gt; Delimiters = SearchValues.Create([',', ';', '|'])\u003c/code\u003e and use \u003ccode\u003etext.AsSpan().IndexOfAny(Delimiters)\u003c/code\u003e. The analyzer is helpful once you know when to act on it.\u003c/p\u003e\n\u003cp\u003eThe most critical mistake is creating instances in loops or hot paths. \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e is powerful but easy to misuse. The analysis work happens at creation time, which is expensive. The lookup cost is cheap. This means you need to store instances as static readonly fields. Pay the creation cost once at startup, then benefit from fast lookups across millions of operations. Creating \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e repeatedly destroys the performance advantage you\u0026rsquo;re trying to gain.\u003c/p\u003e\n\u003cp\u003ePremature optimization is still evil, but there\u0026rsquo;s a clear pattern where \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e delivers real value. The sweet spot is searching for \u003cstrong\u003e3+ different characters\u003c/strong\u003e or values in code that executes \u003cstrong\u003ehundreds to millions of times\u003c/strong\u003e, specifically in hot paths, per-request logic, or tight loops. Variable-length input where you can\u0026rsquo;t predict string size amplifies the benefits. Modern CPUs with SIMD support (most hardware from the last 5-7 years) show the biggest gains, often 5-10x faster. For searching 1-2 characters, just use \u003ccode\u003eIndexOf\u003c/code\u003e directly. It\u0026rsquo;s simpler and performs similarly. Skip \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e for code that runs once at startup, error handling paths that rarely execute, tiny fixed-length strings, or when you\u0026rsquo;re already bottlenecked on I/O operations. The overhead isn\u0026rsquo;t worth it for infrequent operations.\u003c/p\u003e\n\u003cp\u003eThe real test is production, not benchmarks. I\u0026rsquo;ve seen 2x to 10x improvements depending on workload, but your results will vary based on several factors. Character set size matters, where larger sets (10+ characters) benefit more from vectorization than small sets (2-3 characters). String length amplifies the speedup since more characters are processed. Search frequency compounds the benefits over time. CPU architecture plays a role too, with modern SIMD-capable processors showing 5-10x gains while older CPUs show 2-3x improvements. Don\u0026rsquo;t trust microbenchmarks that optimize for cache locality and predictable branching that production never has. Run load tests with production-like data. Measure wall-clock time and monitor allocations, not just throughput.\u003c/p\u003e\n\u003cp\u003ePerformance improvements in production aren\u0026rsquo;t synthetic benchmark gains. They\u0026rsquo;re real, measurable, budget-impacting improvements. Zero-allocation fast paths reduce GC pressure, which compounds in long-running services. Security validators that ran 3-5x faster meant user-facing latency reduction that customers noticed. ETL pipelines that completed 4x faster let us \u003cstrong\u003edecommission servers\u003c/strong\u003e instead of adding them. Not \u0026ldquo;up to\u0026rdquo; savings in a marketing slide. Actual, budgeted, we-turned-off-these-instances savings.\u003c/p\u003e\n\u003cp\u003eNot every use case benefits equally. Profile first, optimize second. Small character sets (2-3 characters) show minimal gains. Large character sets (10+ characters) deliver significant wins. Find your workload\u0026rsquo;s threshold. The creation cost is real, so single searches don\u0026rsquo;t benefit. This optimization is for repeated operations in high-frequency code paths.\u003c/p\u003e\n\u003cp\u003eIn high-throughput APIs and data pipelines, \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e delivered one of the highest ROI optimizations relative to effort. Minimal code changes. No architectural changes. No dependency updates or compatibility breaks. Measurable immediate gains. But ROI requires return. If you\u0026rsquo;re not processing thousands of operations per second, this won\u0026rsquo;t move the needle. Measure your use case and save energy for problems that actually matter.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-bottom-line\"\u003e\u003ca href=\"/posts/searchvalues-saved-us-from-scaling-hell/#the-bottom-line\" title=\"The Bottom Line\"\u003eThe Bottom Line\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e delivers measurable business value when applied correctly, but becomes noise when applied incorrectly. The difference lies in recognizing the pattern: high-frequency operations searching for multiple characters in variable-length input. This pattern shows up in log processing, input validation, CSV parsing, security filtering, and file path sanitization, essentially anywhere you\u0026rsquo;re repeatedly searching for multiple values in strings or spans where the size varies.\u003c/p\u003e\n\u003cp\u003eThe methodology is straightforward, though discipline matters. Start by measuring and profiling your hot paths to identify actual bottlenecks instead of guessing where problems might be. Apply the optimization selectively, focusing on repeated operations with 3+ characters in performance-critical code paths. Then validate the impact through benchmarking with realistic data under realistic load, not synthetic microbenchmarks that optimize for conditions production never sees. Treat CA1870 warnings as a starting point for investigation, not a mandate for action. Your profiler understands your workload better than any static analyzer ever could.\u003c/p\u003e\n\u003cp\u003eThe log sanitization example from earlier demonstrates the core pattern in action: high frequency, variable input, multiple search targets. This same combination appears across input validation, data parsing, security filtering, and content sanitization throughout production systems. When you find this pattern in your codebase, apply \u003ccode\u003eSearchValues\u0026lt;T\u0026gt;\u003c/code\u003e and measure what changes. When it works, it transforms performance in ways that show up directly in infrastructure costs. When it doesn\u0026rsquo;t deliver results, you\u0026rsquo;ve invested an hour learning something valuable about your workload\u0026rsquo;s actual characteristics.\u003c/p\u003e\n\u003cp\u003eThe impact is asymmetric by design. Your infrastructure budget notices the difference immediately through fewer servers, lower costs, and better resource utilization. Your customers notice nothing, which is exactly the point of effective performance optimization. Good performance work is invisible to users while being highly visible in cost structure and operational metrics. One less bottleneck when scaling. One fewer server to provision. One more problem solved before it escalates into a crisis that demands emergency intervention.\u003c/p\u003e\n","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2025-10-20T17:30:00+01:00","id":"https://daily-devops.net/posts/searchvalues-saved-us-from-scaling-hell/","language":"en","summary":"How SearchValues in .NET 8-10 delivered 5x faster string operations, reduced infrastructure costs, and evolved with multi-substring optimization.","tags":["performance","bestpractices","codequality","csharp","dotnet","hidden-gems"],"title":"How SearchValues Saved Us From Scaling Hell","url":"https://daily-devops.net/posts/searchvalues-saved-us-from-scaling-hell/"},{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eMore than thirteen years ago, I was \u003cem\u003eforced\u003c/em\u003e by a project to switch from VB.NET to C# as my primary language. It wasn’t a change I had planned or wanted at the time. VB.NET had its quirks, but it also had a pragmatic and forgiving nature that made certain things feel\u0026hellip; effortless. Over the years, I grew to love C#: its precision, its tooling, its expressiveness. I never looked back.\u003c/p\u003e\n\u003cp\u003eWell – almost never.\u003c/p\u003e\n\u003cp\u003eThere was always one thing I missed. A tiny, elegant helper that VB.NET allowed me to write. A method that I’ve silently wished C# could express as cleanly:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-vb\" data-lang=\"vb\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003e\u0026lt;HideModuleName\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ePublic\u003c/span\u003e \u003cspan class=\"k\"\u003eModule\u003c/span\u003e \u003cspan class=\"nn\"\u003eDisposableExtensions\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c\"\u003e\u0026#39;\u0026#39;\u0026#39; \u0026lt;summary\u0026gt;Tries to release allocated resources.\u0026lt;/summary\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003e    \u0026lt;Extension, DebuggerStepThrough\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ePublic\u003c/span\u003e \u003cspan class=\"k\"\u003eFunction\u003c/span\u003e \u003cspan class=\"nf\"\u003eTryDispose\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003eOf\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"ow\"\u003eAs\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003eClass\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eIDisposable\u003c/span\u003e\u003cspan class=\"p\"\u003e})(\u003c/span\u003e\u003cspan class=\"k\"\u003eByRef\u003c/span\u003e \u003cspan class=\"n\"\u003esource\u003c/span\u003e \u003cspan class=\"ow\"\u003eAs\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"ow\"\u003eAs\u003c/span\u003e \u003cspan class=\"kt\"\u003eBoolean\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eIf\u003c/span\u003e \u003cspan class=\"n\"\u003esource\u003c/span\u003e \u003cspan class=\"ow\"\u003eIs\u003c/span\u003e \u003cspan class=\"k\"\u003eNothing\u003c/span\u003e \u003cspan class=\"k\"\u003eThen\u003c/span\u003e \u003cspan class=\"k\"\u003eReturn\u003c/span\u003e \u003cspan class=\"k\"\u003eTrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eTry\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003esource\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDispose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003esource\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eNothing\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eReturn\u003c/span\u003e \u003cspan class=\"k\"\u003eTrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eCatch\u003c/span\u003e \u003cspan class=\"n\"\u003eex\u003c/span\u003e \u003cspan class=\"ow\"\u003eAs\u003c/span\u003e \u003cspan class=\"n\"\u003eException\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eReturn\u003c/span\u003e \u003cspan class=\"k\"\u003eFalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eEnd\u003c/span\u003e \u003cspan class=\"k\"\u003eTry\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eEnd\u003c/span\u003e \u003cspan class=\"k\"\u003eFunction\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eEnd\u003c/span\u003e \u003cspan class=\"k\"\u003eModule\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis was a genuine life- and time-saver. It not only disposed of an object safely but also \u003cem\u003enulled out\u003c/em\u003e the reference afterwards. In VB.NET, this was possible because extension methods could take parameters \u003cem\u003e\u003ccode\u003eByRef\u003c/code\u003e\u003c/em\u003e – meaning the method could modify the original reference.\u003c/p\u003e\n\u003cp\u003eIn C#, for over a decade, this wasn’t possible. You could write an extension method to call \u003ccode\u003eDispose()\u003c/code\u003e, sure – but you couldn’t also set the caller’s variable to \u003ccode\u003enull\u003c/code\u003e. The language just didn’t allow it.\u003c/p\u003e\n\u003cp\u003eThat missing capability kept this one helper locked in the past, forever trapped in a VB.NET module.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"looking-ahead\"\u003e\u003ca href=\"/posts/still-waiting-for-the-final-piece/#looking-ahead\" title=\"Looking Ahead\"\u003eLooking Ahead\u003c/a\u003e\u003c/h2\u003e\n\n\n\n\n\u003ch3 id=\"net-10-and-c-14-extension-everything\"\u003e\u003ca href=\"/posts/still-waiting-for-the-final-piece/#net-10-and-c-14-extension-everything\" title=\".NET 10 and C# 14: Extension Everything\"\u003e.NET 10 and C# 14: Extension Everything\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eNow with \u003cstrong\u003e.NET 10\u003c/strong\u003e and \u003cstrong\u003eC# 14\u003c/strong\u003e, Microsoft introduces one of the most impactful additions to the language in years: \u003cstrong\u003eExtension Everything\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eThis feature expands what extensions can do. Instead of being limited to methods, extensions can now define \u003cem\u003eproperties\u003c/em\u003e, \u003cem\u003eoperators\u003c/em\u003e, and even participate in pattern matching. The new syntax is expressive, elegant, and feels like the logical next step for C#.\u003c/p\u003e\n\u003cp\u003eHere’s what a modern \u003ccode\u003eTryDispose\u003c/code\u003e might look like using the new \u003ccode\u003eextension\u0026lt;T\u0026gt;\u003c/code\u003e construct:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003enamespace\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kd\"\u003estatic\u003c/span\u003e \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eIDisposableExtensions\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eextension\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"n\"\u003edisposable\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003ewhere\u003c/span\u003e \u003cspan class=\"n\"\u003eT\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eIDisposable\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kd\"\u003epublic\u003c/span\u003e \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e \u003cspan class=\"n\"\u003eTryDispose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edisposable\u003c/span\u003e \u003cspan class=\"k\"\u003eis\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003etry\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003edisposable\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDispose\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003edisposable\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003edefault\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003ecatch\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eClean. Expressive. \u003cstrong\u003eImagine this:\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003estream\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eFile\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eOpenRead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Lots of code...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(!\u003c/span\u003e\u003cspan class=\"n\"\u003estream\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eTryDispose\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWarn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Could not release stream properly.\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt reads perfectly. It’s elegant. And yet today, it’s still only half real. For now, the value won’t become \u003ccode\u003enull\u003c/code\u003e. The compiler still passes a copy. The concept is ready, but the implementation is not.\u003c/p\u003e\n\u003cp\u003eExactly what we’ve been waiting for.\u003c/p\u003e\n\u003cp\u003eExcept\u0026hellip; not quite.\u003c/p\u003e\n\u003cp\u003eWhile the syntax is here, the semantics aren’t – at least not yet. The extended instance (\u003ccode\u003edisposable\u003c/code\u003e) behaves as a copy of the reference, not a true alias to the caller’s variable. So even though the method compiles and runs, the original variable won’t be set to \u003ccode\u003enull\u003c/code\u003e afterwards. The dream of safely disposing and nulling in a single step remains just out of reach.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"why-it-still-matters\"\u003e\u003ca href=\"/posts/still-waiting-for-the-final-piece/#why-it-still-matters\" title=\"Why It Still Matters\"\u003eWhy It Still Matters\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThis syntax is more than just an aesthetic change—it represents an intentional step toward making the language more expressive and aligned with real-world usage. It points in the right direction, even if the final step hasn’t landed yet.\u003c/p\u003e\n\u003cp\u003eThe idea is simple: bring intent and safety together. Give developers the ability to describe what should happen to a resource without extra ceremony. Whether it’s cleanup, resetting, or transformation—such syntax makes intent explicit and code safer.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"closing-thoughts\"\u003e\u003ca href=\"/posts/still-waiting-for-the-final-piece/#closing-thoughts\" title=\"Closing Thoughts\"\u003eClosing Thoughts\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThis evolution in syntax feels like the final stretch in a journey that began more than a decade ago. It’s tantalizingly close—a feature that captures the spirit of what VB.NET once made easy, expressed in the elegance of modern C#.\u003c/p\u003e\n\u003cp\u003eVB.NET was always about readability and pragmatism. C# built on that foundation with structure and precision. With \u003cem\u003eExtension Everything\u003c/em\u003e, the two worlds almost meet—but not entirely. Not yet.\u003c/p\u003e\n\u003cp\u003eFor me, that’s both exciting and frustrating. The language can now express the shape of what I’ve wanted for years, but the behavior still isn’t there. So, I’ll keep that old VB.NET module around a little longer — partly out of nostalgia, partly because C# is still a step behind its little brother in this very specific trick. Even though VB.NET no longer holds any real relevance in my work, it’s amusing to see that it still has this one ace up its sleeve.\u003c/p\u003e\n\u003cp\u003eBut we’re getting close. Very close. \u003cstrong\u003eStill waiting for the final piece.\u003c/strong\u003e\u003c/p\u003e\n","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2025-10-06T09:00:00+02:00","id":"https://daily-devops.net/posts/still-waiting-for-the-final-piece/","language":"en","summary":"C# 14’s new 'Extension Everything' syntax comes close to VB.NET’s ByRef magic—but not quite. A witty look at what’s still missing in modern .NET.\n","tags":["csharp","dotnet","extensions","hidden-gems","vbnet","visualstudio"],"title":"Still Waiting for the Final Piece: C# 14 Comes Close","url":"https://daily-devops.net/posts/still-waiting-for-the-final-piece/"},{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eIn C#, the \u003ccode\u003eStringValues\u003c/code\u003e struct belongs to the \u003ccode\u003eMicrosoft.Extensions.Primitives\u003c/code\u003e namespace, which is widely used in modern .NET applications. This struct plays a crucial role in efficiently managing string collections, especially when handling efficiently, particularly in contexts where multiple strings are involved. In this blog post, we’ll explore the purpose, usage, and key features of the \u003ccode\u003eStringValues\u003c/code\u003e struct in C#.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"what-is-the-stringvalues-struct\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#what-is-the-stringvalues-struct\" title=\"What is the StringValues struct?\"\u003eWhat is the \u003ccode\u003eStringValues\u003c/code\u003e struct?\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe \u003ccode\u003eStringValues\u003c/code\u003e struct is designed to hold one or more strings in a way that is optimized for performance in scenarios where a string might be passed as a single value or as a collection of values. Unlike other collections such as arrays or lists, \u003ccode\u003eStringValues\u003c/code\u003e is designed to offer a lightweight and efficient solution for managing strings that may vary in number, especially when it’s important to reduce memory allocations.\u003c/p\u003e\n\u003cp\u003eThis struct was introduced to cater to scenarios involving HTTP headers, query strings, and form values, where multiple values for the same key are common. For instance, when you query an HTTP endpoint that can return multiple values for a single key, \u003ccode\u003eStringValues\u003c/code\u003e allows the application to work with them in a simple, type-safe manner.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"key-characteristics-of-stringvalues\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#key-characteristics-of-stringvalues\" title=\"Key Characteristics of StringValues\"\u003eKey Characteristics of \u003ccode\u003eStringValues\u003c/code\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eSingle or Multiple Strings\u003c/strong\u003e:\nA \u003ccode\u003eStringValues\u003c/code\u003e object can represent either a single string or an array of strings. This allows developers to handle cases where a string value is expected but there may also be instances where multiple string values are present under the same key.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003ePerformance\u003c/strong\u003e:\nOne of the major benefits of the \u003ccode\u003eStringValues\u003c/code\u003e struct is its focus on performance. By internally managing strings with minimal allocations, \u003ccode\u003eStringValues\u003c/code\u003e helps reduce memory overhead, especially when dealing with multiple string values that could potentially involve lots of garbage collection in a high-performance application.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eValue Semantics\u003c/strong\u003e:\n\u003ccode\u003eStringValues\u003c/code\u003e can be implicitly converted to a \u003ccode\u003estring[]\u003c/code\u003e or a single \u003ccode\u003estring\u003c/code\u003e. It also supports indexing and enumeration, making it easy to work with the contained values, whether there is just one string or multiple.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\n\n\n\n\u003ch2 id=\"basic-usage-of-stringvalues\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#basic-usage-of-stringvalues\" title=\"Basic Usage of StringValues\"\u003eBasic Usage of \u003ccode\u003eStringValues\u003c/code\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eTo better understand how the \u003ccode\u003eStringValues\u003c/code\u003e struct works, let\u0026rsquo;s take a look at some basic examples.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"single-string-value\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#single-string-value\" title=\"Single String Value\"\u003eSingle String Value\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eIf you are dealing with a case where you expect only a single string, you can use \u003ccode\u003eStringValues\u003c/code\u003e like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eMicrosoft.Extensions.Primitives\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e \u003cspan class=\"n\"\u003evalues\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Hello, World!\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Access the string directly\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"c1\"\u003e// Output: Hello, World!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Access the string via array index\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e \u003cspan class=\"c1\"\u003e// Output: Hello, World!\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn this example, we create a \u003ccode\u003eStringValues\u003c/code\u003e instance with a single string \u003ccode\u003e\u0026quot;Hello, World!\u0026quot;\u003c/code\u003e. The \u003ccode\u003eStringValues\u003c/code\u003e struct allows us to easily access the string as if it were a regular string, but the underlying type is still flexible enough to accommodate other scenarios.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"multiple-string-values\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#multiple-string-values\" title=\"Multiple String Values\"\u003eMultiple String Values\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eIn cases where multiple strings might be associated with a single key, such as in query parameters or HTTP headers, \u003ccode\u003eStringValues\u003c/code\u003e handles this gracefully. Here\u0026rsquo;s an example of how you would initialize a \u003ccode\u003eStringValues\u003c/code\u003e with multiple strings:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eMicrosoft.Extensions.Primitives\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e \u003cspan class=\"n\"\u003evalues\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;First Value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Second Value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Third Value\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Access individual values\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e \u003cspan class=\"c1\"\u003e// Output: First Value\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e \u003cspan class=\"c1\"\u003e// Output: Second Value\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e \u003cspan class=\"c1\"\u003e// Output: Third Value\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Iterate through all values\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"k\"\u003evalue\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn this case, \u003ccode\u003eStringValues\u003c/code\u003e holds an array of strings. You can access the strings using indexing and can also enumerate through all the contained values with a simple \u003ccode\u003eforeach\u003c/code\u003e loop.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"implicit-conversion-to-string\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#implicit-conversion-to-string\" title=\"Implicit Conversion to string[]\"\u003eImplicit Conversion to \u003ccode\u003estring[]\u003c/code\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eYou can also implicitly convert \u003ccode\u003eStringValues\u003c/code\u003e to an array of strings. This makes it easy to interact with the object in contexts where an array is expected, such as when passing the value to methods that require a string array.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eMicrosoft.Extensions.Primitives\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e \u003cspan class=\"n\"\u003evalues\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Apple\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Banana\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Cherry\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Implicit conversion to string[]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e[]\u003c/span\u003e \u003cspan class=\"n\"\u003estringArray\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Print out the values\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"k\"\u003evalue\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003estringArray\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis ability to implicitly convert to a \u003ccode\u003estring[]\u003c/code\u003e makes the \u003ccode\u003eStringValues\u003c/code\u003e struct highly compatible with other parts of the .NET ecosystem.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"advanced-features-of-stringvalues\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#advanced-features-of-stringvalues\" title=\"Advanced Features of StringValues\"\u003eAdvanced Features of \u003ccode\u003eStringValues\u003c/code\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eApart from the basic functionality, the \u003ccode\u003eStringValues\u003c/code\u003e struct has a few more advanced features that can be useful in certain scenarios.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"checking-for-empty-or-null-values\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#checking-for-empty-or-null-values\" title=\"Checking for Empty or Null Values\"\u003eChecking for Empty or Null Values\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThe \u003ccode\u003eStringValues\u003c/code\u003e struct provides a way to check whether it contains any values, or if it’s essentially empty. You can use the \u003ccode\u003eIsNullOrEmpty\u003c/code\u003e property to make such checks:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eMicrosoft.Extensions.Primitives\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e \u003cspan class=\"n\"\u003evalues\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eIsNullOrEmpty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;No values present\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Adding a value\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003evalues\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Hello, World!\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(!\u003c/span\u003e\u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eIsNullOrEmpty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evalues\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;There is a value present\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\n\n\n\u003ch3 id=\"stringvalues-as-a-dictionary-key\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#stringvalues-as-a-dictionary-key\" title=\"StringValues as a Dictionary Key\"\u003eStringValues as a Dictionary Key\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eStringValues\u003c/code\u003e is also designed to work as a key in dictionaries. This can be especially useful when you are dealing with collections of query parameters or HTTP headers in web applications, where the same key might have multiple associated values.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eMicrosoft.Extensions.Primitives\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eDictionary\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003edictionary\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eDictionary\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003enew\u003c/span\u003e \u003cspan class=\"n\"\u003eStringValues\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edictionary\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Some value associated with multiple keys\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edictionary\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e]);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIn this example, \u003ccode\u003eStringValues\u003c/code\u003e can be used as a dictionary key, allowing the association of a string value with one or more key values.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"when-to-use-stringvalues\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#when-to-use-stringvalues\" title=\"When to Use StringValues\"\u003eWhen to Use \u003ccode\u003eStringValues\u003c/code\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe \u003ccode\u003eStringValues\u003c/code\u003e struct shines in scenarios where you expect multiple values for a single key, such as:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eHTTP Headers\u003c/strong\u003e: Web applications often need to manage headers with multiple values. For instance, a header like \u003ccode\u003eAccept-Language\u003c/code\u003e could contain multiple languages.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eQuery Parameters\u003c/strong\u003e: When dealing with query strings, a single parameter might appear multiple times with different values.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eForm Data\u003c/strong\u003e: Similar to query parameters, form fields can also contain multiple values for the same key.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIn such scenarios, \u003ccode\u003eStringValues\u003c/code\u003e provides an elegant and efficient way to manage string collections without the need for more complex structures like lists or arrays.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"conclusion\"\u003e\u003ca href=\"/posts/understanding-csharp-stringvalues/#conclusion\" title=\"Conclusion\"\u003eConclusion\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe \u003ccode\u003eStringValues\u003c/code\u003e struct in C# is an essential tool for developers working with scenarios where one or more strings need to be handled efficiently. By supporting both single and multiple values, being lightweight and optimized for performance, and offering easy integration with other .NET components, it simplifies the management of string collections, particularly in web applications and services.\u003c/p\u003e\n\u003cp\u003eWhether you\u0026rsquo;re dealing with HTTP headers, query parameters, or form data, understanding and leveraging the \u003ccode\u003eStringValues\u003c/code\u003e struct can help you write cleaner, more efficient code. By utilizing its features, developers can manage string collections more effectively, reducing memory overhead and increasing the performance of their applications.\u003c/p\u003e","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2024-12-30T16:00:00+01:00","id":"https://daily-devops.net/posts/understanding-csharp-stringvalues/","language":"en","summary":"Learn about C# StringValues type, its features, usage patterns, and performance benefits in efficiently handling string values in modern .NET applications.","tags":["csharp","dotnet","hidden-gems"],"title":"Understanding the C# `StringValues`: A Comprehensive Guide","url":"https://daily-devops.net/posts/understanding-csharp-stringvalues/"},{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eIn the ever-evolving world of .NET development, managing project configurations effectively is crucial for maintaining a clean and efficient build process. One of the less frequently discussed but highly useful properties is \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e. This property, when correctly utilized, can streamline your build process and ensure that your project is configured properly depending on the build environment. In this article, we\u0026rsquo;ll explore the \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e property with concrete examples and discuss best practices for using it effectively.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"understanding-the-buildinginsidevisualstudio-property\"\u003e\u003ca href=\"/posts/buildinginsidevisualstudio/#understanding-the-buildinginsidevisualstudio-property\" title=\"Understanding the BuildingInsideVisualStudio Property\"\u003eUnderstanding the \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e Property\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e property is a conditional flag that can be used within your project files (.csproj) to apply certain settings or include/exclude packages and references based on whether the project is being built inside Visual Studio. This property is particularly useful when you need to differentiate between builds triggered from Visual Studio and those triggered from other environments such as command-line builds or CI/CD pipelines. See also \u003ca href=\"https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eMSBuild conditions\u003c/a\u003e and \u003ca href=\"https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-properties\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003ecommon project properties\u003c/a\u003e for context.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"example-adding-a-package-reference-conditionally\"\u003e\u003ca href=\"/posts/buildinginsidevisualstudio/#example-adding-a-package-reference-conditionally\" title=\"Example: Adding a Package Reference Conditionally\"\u003eExample: Adding a Package Reference Conditionally\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eLet\u0026rsquo;s start with a practical example: adding a package reference only when the project is being built inside Visual Studio. This can be useful when you want to include certain tools or analyzers only in the development environment to keep the build lean for production.\u003c/p\u003e\n\u003cp\u003eAssuming you want to add a reference to \u003ccode\u003eSonarAnalyzer.CSharp\u003c/code\u003e, a popular static code analysis tool, but only when building the project within Visual Studio, you can use the \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e property to conditionally include this package reference in your \u003ccode\u003e.csproj\u003c/code\u003e file. Why would you want to do this? It\u0026rsquo;s already included in your CI/CD pipeline, so you don\u0026rsquo;t need it in your local development environment? The answer is simple: you want to have the same code analysis rules and hints in your local development environment as in your CI/CD pipeline. This way, you can fix issues early and avoid surprises when pushing your code to the repository, and executing maybe long-running CI/CD pipelines.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s how you can do it:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;Project\u003c/span\u003e \u003cspan class=\"na\"\u003eSdk=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Microsoft.NET.Sdk\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;ItemGroup\u003c/span\u003e \u003cspan class=\"na\"\u003eCondition=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#39;$(BuildingInsideVisualStudio)\u0026#39; == \u0026#39;true\u0026#39;\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;PackageReference\u003c/span\u003e \u003cspan class=\"na\"\u003eInclude=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SonarAnalyzer.CSharp\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eVersion=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;9.6.0\u0026#34;\u003c/span\u003e \u003cspan class=\"nt\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;/ItemGroup\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c\"\u003e\u0026lt;!-- Rest of the project file --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/Project\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\n\n\n\u003ch2 id=\"beware-of-pitfalls-with-buildinginsidevisualstudio\"\u003e\u003ca href=\"/posts/buildinginsidevisualstudio/#beware-of-pitfalls-with-buildinginsidevisualstudio\" title=\"Beware of Pitfalls with BuildingInsideVisualStudio\"\u003eBeware of Pitfalls with \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWhile the \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e property is a powerful tool for customizing your build process, there are some common pitfalls to be aware of when using it in your project files. Let\u0026rsquo;s explore these pitfalls and how to avoid them.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"expectation-that-buildinginsidevisualstudio-is-configured-correctly\"\u003e\u003ca href=\"/posts/buildinginsidevisualstudio/#expectation-that-buildinginsidevisualstudio-is-configured-correctly\" title=\"Expectation that BuildingInsideVisualStudio is configured correctly\"\u003eExpectation that \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e is configured correctly\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eWhen using the \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e property, it\u0026rsquo;s important to remember that it may not always be set to \u003ccode\u003etrue\u003c/code\u003e. For example, when building the project outside of Visual Studio, this property may be empty or set to a different value. Relying on the assumption that \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e will always be \u003ccode\u003etrue\u003c/code\u003e or \u003ccode\u003efalse\u003c/code\u003e can lead to unexpected behavior and misconfigurations.\u003c/p\u003e\n\u003cp\u003eWhen using conditional checks in your project files, it\u0026rsquo;s essential to ensure that the property values are correctly set and evaluated. Misconfiguring the property values can lead to unexpected behavior or missing configurations. In the case of \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e, the only valid fact is \u003ccode\u003etrue\u003c/code\u003e when the project is built inside Visual Studio. Otherwise, it could be \u003ccode\u003efalse\u003c/code\u003e or empty.\u003c/p\u003e\n\u003cp\u003eTo avoid issues where the property might be empty or not set, you should use a condition that checks against \u003ccode\u003etrue\u003c/code\u003e explicitly. This ensures that the feature is enabled only when the property is explicitly set to \u003ccode\u003etrue\u003c/code\u003e. Here\u0026rsquo;s an example of a conditional property check:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;Project\u003c/span\u003e \u003cspan class=\"na\"\u003eSdk=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Microsoft.NET.Sdk\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c\"\u003e\u0026lt;!-- Instead of this 👎 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;ItemGroup\u003c/span\u003e \u003cspan class=\"na\"\u003eCondition=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#39;$(BuildingInsideVisualStudio)\u0026#39; == \u0026#39;false\u0026#39;\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;PackageReference\u003c/span\u003e \u003cspan class=\"na\"\u003eInclude=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;FeatureXPackage\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eVersion=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;1.0.0\u0026#34;\u003c/span\u003e \u003cspan class=\"nt\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;/ItemGroup\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c\"\u003e\u0026lt;!-- Use this 👍 --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;ItemGroup\u003c/span\u003e \u003cspan class=\"na\"\u003eCondition=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#39;$(BuildingInsideVisualStudio)\u0026#39; != \u0026#39;true\u0026#39;\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;PackageReference\u003c/span\u003e \u003cspan class=\"na\"\u003eInclude=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;FeatureXPackage\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eVersion=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;1.0.0\u0026#34;\u003c/span\u003e \u003cspan class=\"nt\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;/ItemGroup\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/Project\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\n\n\n\u003ch4 id=\"benefits\"\u003e\u003ca href=\"/posts/buildinginsidevisualstudio/#benefits\" title=\"Benefits\"\u003eBenefits\u003c/a\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eAvoids Null or Empty Values:\u003c/strong\u003e With a condition like \u003ccode\u003e!= 'true'\u003c/code\u003e, you ensure that the feature is enabled only when the property is not explicitly set to \u003ccode\u003etrue\u003c/code\u003e, avoiding issues with null or empty values.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eConsistent Behavior:\u003c/strong\u003e This approach ensures consistent behavior across different environments and build scenarios.\u003c/li\u003e\n\u003c/ul\u003e\n\n\n\n\n\u003ch4 id=\"potential-errors\"\u003e\u003ca href=\"/posts/buildinginsidevisualstudio/#potential-errors\" title=\"Potential Errors\"\u003ePotential Errors\u003c/a\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eMisconfigured Conditions:\u003c/strong\u003e Incorrectly configured conditions can lead to unexpected behavior or missing configurations. Always test your project settings thoroughly.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eUnintended Enabling:\u003c/strong\u003e Be cautious of unintended enabling of features or dependencies if the property is not set as expected.\u003c/li\u003e\n\u003c/ul\u003e\n\n\n\n\n\u003ch3 id=\"assumption-that-this-works-in-other-ides\"\u003e\u003ca href=\"/posts/buildinginsidevisualstudio/#assumption-that-this-works-in-other-ides\" title=\"Assumption that this works in other IDEs\"\u003eAssumption that this works in other IDEs\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eIn general assumptions are bad. The \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e property is a Visual Studio specific property. It is not guaranteed to work in other IDEs like Visual Studio Code, or any other IDE. If you want to have the same behavior in other IDEs, you have to check if the IDE supports this property or if there is a similar property available. Like in JetBrains Rider, you can use the \u003ccode\u003eBuildingByReSharper\u003c/code\u003e property.\u003c/p\u003e\n\n\n\n\n\u003ch4 id=\"potential-errors-1\"\u003e\u003ca href=\"/posts/buildinginsidevisualstudio/#potential-errors-1\" title=\"Potential Errors\"\u003ePotential Errors\u003c/a\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eIncompatibility with Other IDEs:\u003c/strong\u003e Relying solely on \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e can lead to incompatibility issues when using other IDEs or build environments. Always check the compatibility of the property with your target environment.\u003c/li\u003e\n\u003c/ul\u003e\n\n\n\n\n\u003ch2 id=\"conclusion\"\u003e\u003ca href=\"/posts/buildinginsidevisualstudio/#conclusion\" title=\"Conclusion\"\u003eConclusion\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe \u003ccode\u003eBuildingInsideVisualStudio\u003c/code\u003e property and conditional checks in your .NET project files offer powerful ways to customize your build process depending on the environment. By following best practices such as checking conditions against \u003ccode\u003etrue\u003c/code\u003e and being mindful of how properties are used, you can avoid common pitfalls and optimize your build configurations for different scenarios.\u003c/p\u003e\n\u003cp\u003eLeveraging these techniques not only helps in maintaining a clean and efficient build process but also ensures that your project is configured correctly across various development environments. As always, thorough testing and careful configuration are key to making the most out of these features.\u003c/p\u003e\n\u003cp\u003eFeel free to incorporate these examples into your own projects and see how they can simplify and improve your build process. Happy coding!\u003c/p\u003e","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2024-09-10T17:00:00+02:00","id":"https://daily-devops.net/posts/buildinginsidevisualstudio/","language":"en","summary":"Learn how to use the BuildingInsideVisualStudio property in .NET to conditionally include packages, optimize builds, and streamline developer workflows.","tags":["msbuild","visualstudio","bestpractices","csharp","dotnet","hidden-gems"],"title":"BuildingInsideVisualStudio: .NET Project Properties","url":"https://daily-devops.net/posts/buildinginsidevisualstudio/"},{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eFor over 12 years, NuGet package management has been part of the .NET ecosystem with direct integrations to various IDEs, CLIs and build systems. But a feature took 12 years before it appeared and certainly needs some more maintenance until it is mature!\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-issue\"\u003e\u003ca href=\"/posts/manage-nuget-packages-centrally/#the-issue\" title=\"The issue\"\u003eThe issue\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eRegardless of the code version management strategy, mono-repository vs. poly-repository, there has always been a need to synchronize the individual projects in the versions of NuGet packages used. Reasons for this are compatibility and security, but also new functionalities or bug fixes.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"earlier-approaches\"\u003e\u003ca href=\"/posts/manage-nuget-packages-centrally/#earlier-approaches\" title=\"Earlier approaches\"\u003eEarlier approaches\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eOver the years, the requirements in this area have evolved more and more, so that the previous solution approaches increasingly reached their limits. Not only the uniform use of the same package version, but also the general use of a package in all related projects of a solution was taken up and developed further in this context. However, the main shortcoming could never be solved; until now, manual intervention by a developer was always necessary to update the version of the packages used. The existing integrations of IDEs and CLIs produced more errors than they could fix.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"central-package-management-cpm\"\u003e\u003ca href=\"/posts/manage-nuget-packages-centrally/#central-package-management-cpm\" title=\"Central Package Management (CPM)\"\u003eCentral Package Management (CPM)\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNow the request has been fulfilled and in April 2022 the \u003ca href=\"https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eCentral Package Management (\u0026ldquo;CPM\u0026rdquo;)\u003c/a\u003e was introduced and released along with NuGet version 6.2 and some complementary features.\u003c/p\u003e\n\u003cp\u003eTo enable central package management, the MSBuild property \u003ccode\u003eManagePackageVersionsCentrally\u003c/code\u003e is set to \u003ccode\u003etrue\u003c/code\u003e in the \u003ccode\u003eDirectory.Packages.props\u003c/code\u003e file.\u003c/p\u003e\n\u003cp\u003eFor version listing and management, \u003ccode\u003ePackageVersion\u003c/code\u003e elements are required, each containing the package name and the version to be used. The next step is to remove the \u003ccode\u003eVersion\u003c/code\u003e attribute from all \u003ccode\u003ePackageReference\u003c/code\u003e elements in the project files. This migrates the solution and it will use the central package management from now on.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"additional-feature-transitive-pinning\"\u003e\u003ca href=\"/posts/manage-nuget-packages-centrally/#additional-feature-transitive-pinning\" title=\"Additional feature: Transitive pinning\"\u003eAdditional feature: Transitive pinning\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eSetting the MSBuild property \u003ccode\u003eCentralPackageTransitivePinningEnabled\u003c/code\u003e to \u003ccode\u003etrue\u003c/code\u003e tells NuGet to update all transitive dependencies from their explicitly defined dependencies. This property can be set in both \u003ca href=\"https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022#directorybuildprops-and-directorybuildtargets\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003e\u003ccode\u003eDirectory.Build.props\u003c/code\u003e\u003c/a\u003e and the aforementioned \u003ccode\u003eDirectory.Packages.props\u003c/code\u003e.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"additional-feature-global-package-references\"\u003e\u003ca href=\"/posts/manage-nuget-packages-centrally/#additional-feature-global-package-references\" title=\"Additional feature: Global Package References\"\u003eAdditional feature: Global Package References\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eAnother feature is \u003ccode\u003eGlobalPackageReference\u003c/code\u003e, which can be used to reference a package in any project of the solution / repository, such as code analyzer. This kind of package referencing should also be done in \u003ccode\u003eDirectory.Packages.props\u003c/code\u003e.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"summary\"\u003e\u003ca href=\"/posts/manage-nuget-packages-centrally/#summary\" title=\"Summary\"\u003eSummary\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAll in all, a great enhancement to the NuGet system. However, there are currently some issues with the Visual Studio or .NET CLI integration.\u003c/p\u003e\n\u003cp\u003eBoth integrations are able to evaluate the package references and recover the packages. However, when updating with Visual Studio, the XML structure of the project is updated incorrectly, so manual rework is required.\u003c/p\u003e\n\u003cp\u003eWhen the .NET CLI wants to add a reference to a project, CPM is ignored and build errors occur again.\u003c/p\u003e\n\u003cp\u003eHowever, this should not deter you, because existing integrations such as \u003ca href=\"https://github.com/dependabot\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eGitHubs Dependabot\u003c/a\u003e provide excellent results.\u003c/p\u003e","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2023-04-17T08:30:00+02:00","id":"https://daily-devops.net/posts/manage-nuget-packages-centrally/","language":"en","summary":"Learn how to centrally manage NuGet packages in .NET solutions using Directory.Packages.props for better dependency management and version control.","tags":["nuget","bestpractices","csharp","dependency-management","dotnet","hidden-gems","technicaldebt"],"title":"Manage NuGet Packages Centrally","url":"https://daily-devops.net/posts/manage-nuget-packages-centrally/"}],"language":"en","title":"Hidden Gems in Software Development on Daily DevOps \u0026 .NET","version":"https://jsonfeed.org/version/1.1"}