<?xml version="1.0" encoding="UTF-8"?>
<rss 
    version="2.0"
    xmlns:dc="http://purl.org/dc/elements/1.1/" 
    xmlns:content="http://purl.org/rss/1.0/modules/content/" 
    xmlns:atom="http://www.w3.org/2005/Atom" 
    xmlns:media="http://search.yahoo.com/mrss/" 
>
    <channel>
        <title><![CDATA[Tech with Christian]]></title>
        <description><![CDATA[A Pragmatic Solution Architect &amp; Software Engineer sharing his knowledge with the internet.]]></description>
        <link>https://christian-schou.com</link>
        <image>
            <url>https://christian-schou.com/favicon.png</url>
            <title>Tech with Christian</title>
            <link>https://christian-schou.com</link>
        </image>
        <generator>Ghost 6.35</generator>
        <lastBuildDate>Thu, 21 May 2026 12:52:32 +0200</lastBuildDate>
        <atom:link href="https://christian-schou.com" rel="self" type="application/rss+xml"/>
        <ttl>60</ttl>

                <item>
                    <title><![CDATA[BCForge Trusted by]]></title>
                    <description><![CDATA[Trusted by AL development teams across the Business Central ecosystem











Microsoft Partners














ISV Developers














BC Consultancies














Enterprise AL teams]]></description>
                    <link>https://christian-schou.com/bcforge/</link>
                    <guid isPermaLink="false">69e8b770d3c29600018cc8ff</guid>


                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Wed, 22 Apr 2026 13:58:55 +0200</pubDate>


                    <content:encoded><![CDATA[<p>Trusted by AL development teams across the Business Central ecosystem</p><div class="kg-card kg-product-card">
            <div class="kg-product-card-container">
                
                <div class="kg-product-card-title-container">
                    <h4 class="kg-product-card-title"></h4>
                </div>
                

                <div class="kg-product-card-description"><p dir="ltr"><span style="white-space: pre-wrap;">Microsoft Partners</span></p></div>
                
            </div>
        </div><div class="kg-card kg-product-card">
            <div class="kg-product-card-container">
                
                <div class="kg-product-card-title-container">
                    <h4 class="kg-product-card-title"></h4>
                </div>
                

                <div class="kg-product-card-description"><p dir="ltr"><span style="white-space: pre-wrap;">ISV Developers</span></p></div>
                
            </div>
        </div><div class="kg-card kg-product-card">
            <div class="kg-product-card-container">
                
                <div class="kg-product-card-title-container">
                    <h4 class="kg-product-card-title"></h4>
                </div>
                

                <div class="kg-product-card-description"><p dir="ltr"><span style="white-space: pre-wrap;">BC Consultancies</span></p></div>
                
            </div>
        </div><div class="kg-card kg-product-card">
            <div class="kg-product-card-container">
                
                <div class="kg-product-card-title-container">
                    <h4 class="kg-product-card-title"></h4>
                </div>
                

                <div class="kg-product-card-description"><p dir="ltr"><span style="white-space: pre-wrap;">Enterprise AL teams</span></p></div>
                
            </div>
        </div>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[BCForge]]></title>
                    <description><![CDATA[BCForge enforces your AL coding standards on every pull request, manages BC object ID ranges across your portfolio, and connects AI assistants directly to your workspace rules and data via MCP.

 * AL code governance
 * Powered by AI]]></description>
                    <link>https://christian-schou.com/bcforge/</link>
                    <guid isPermaLink="false">69e8b47fd3c29600018cc8d1</guid>


                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Wed, 22 Apr 2026 13:53:51 +0200</pubDate>


                    <content:encoded><![CDATA[<p>BCForge enforces your AL coding standards on every pull request, manages BC object ID ranges across your portfolio, and connects AI assistants directly to your workspace rules and data via MCP.</p><ul><li>AL code governance</li><li>Powered by AI</li></ul>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Branch Name Validator]]></title>
                    <description><![CDATA[Here goes the text]]></description>
                    <link>https://christian-schou.com/docs/branch-name-validator/branch-name-validator/</link>
                    <guid isPermaLink="false">69cb9af7a3a6c0000196db1e</guid>

                        <category><![CDATA[Branch Name Validator]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Tue, 31 Mar 2026 12:07:53 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2026/03/git-branch-duotone.svg" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2026/03/git-branch-duotone.svg" alt="Branch Name Validator"/> <p>Here goes the text</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Is Your Business Central Ready for Copilot?]]></title>
                    <description><![CDATA[Microsoft Copilot is not something for the future, it is already embedded within Business Central, and it is changing how finance teams, sales departments, and operations managers get work done.

However a thing has come to my mind after reading a lot of community questions, that I would like to]]></description>
                    <link>https://christian-schou.com/blog/is-your-business-central-ready-for-copilot/</link>
                    <guid isPermaLink="false">699d41e8a3a6c0000196da2e</guid>

                        <category><![CDATA[Business Central 365]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Wed, 25 Feb 2026 12:35:42 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2026/02/are-you-ready-for-copilot-in-business-central-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2026/02/are-you-ready-for-copilot-in-business-central-twc.webp" alt="Is Your Business Central Ready for Copilot?"/> <p>Microsoft Copilot is not something for the future, it is already embedded within Business Central, and it is changing how finance teams, sales departments, and operations managers get work done.</p><p>However a thing has come to my mind after reading a lot of community questions, that I would like to address. Having Copilot available and being <em>ready</em> for Copilot are two very different things.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://copilot.microsoft.com/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Microsoft Copilot: Your AI companion</div><div class="kg-bookmark-description">Microsoft Copilot is your companion to inform, entertain and inspire. Get advice, feedback and straightforward answers. Try Copilot now.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon.svg" alt=""><span class="kg-bookmark-author">Microsoft Copilot: Your AI companion</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/meta-image.jpg" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>I am sure you have read about AI-assisted workflows and seen the videos and gifs that shows you how much power they give you. Before you get too excited about these, you should be doing an honest go-through of your current setup.</p><p>Those who get's the most from Copilot are not necessarily those who hits the activate button first, it's the companies who actually prepared their environment before they enabled it.</p><p>To help you figure out where you are in that process, I have created a practical checklist you can read through and use within your company.</p><h2 id="are-you-actually-running-business-central-online">Are You Actually Running Business Central Online?</h2><p>When you acquire a license for Business Central, Copilot is automatically included. There no premium tier you have to unlock or an add-on that needs a purchase. <strong>There is only one requirement</strong> that catches a lot off guard.. it's only available for Business Central Online. If you are running your Business Central instance in an on-premises or private cloud, Copilot simply won't be there.</p><p>If you are running an instance in the cloud, you must activate Copilot, as it doesn't happen by itself. An administrator (probably you) must expecitly enable the capabilities at the <strong>Copilot and agent capabilities page</strong>, read and accept the data usage terms, and finally configure which features are available to which users in your organization.</p><figure class="kg-card kg-image-card"><img src="https://christian-schou.com/content/images/2026/02/copilot-and-agent-capabilities-features.webp" class="kg-image" alt="" loading="lazy" width="2000" height="1511" srcset="https://christian-schou.com/content/images/size/w600/2026/02/copilot-and-agent-capabilities-features.webp 600w, https://christian-schou.com/content/images/size/w1000/2026/02/copilot-and-agent-capabilities-features.webp 1000w, https://christian-schou.com/content/images/size/w1600/2026/02/copilot-and-agent-capabilities-features.webp 1600w, https://christian-schou.com/content/images/2026/02/copilot-and-agent-capabilities-features.webp 2022w" sizes="(min-width: 720px) 720px"></figure><p>You also have to consider data movement if you are hosting your environment outside the default Azure geography.</p><figure class="kg-card kg-image-card"><img src="https://christian-schou.com/content/images/2026/02/copilot-and-agent-capabilities-data-movement.webp" class="kg-image" alt="" loading="lazy" width="2000" height="695" srcset="https://christian-schou.com/content/images/size/w600/2026/02/copilot-and-agent-capabilities-data-movement.webp 600w, https://christian-schou.com/content/images/size/w1000/2026/02/copilot-and-agent-capabilities-data-movement.webp 1000w, https://christian-schou.com/content/images/size/w1600/2026/02/copilot-and-agent-capabilities-data-movement.webp 1600w, https://christian-schou.com/content/images/2026/02/copilot-and-agent-capabilities-data-movement.webp 2032w" sizes="(min-width: 720px) 720px"></figure><p><strong>The question</strong> - Are you on Business Central Online? And has anyone in your organisation actually walked through the Copilot setup steps yet?</p><h2 id="is-your-data-clean-enough-to-trust-ai-suggestions">Is Your Data Clean Enough to Trust AI Suggestions?</h2><p>Copilot is an AI agent and it can only be as good as the data you are feeding it. If we take a look at the <strong>bank reconciliation assist</strong>, it uses your historical transaction data and G/L (General Ledger) account mapping to suggest matches.</p><p>If you got a cluttered chart of accounts, your bank feeds are inconsistent, or old transactions has been posted incorrectly, Copilot will give you suggestions that you simply cannot trust. That will not help you, it will create more work..</p><p>Same thing goes for <strong>document matching and e-documents</strong>. Copilot is intelligent enough to match incoming e-invoices to your purchase orders, but only if your vendor records, item references, and document workflows are maintained. Shit in, shit out - AI will not help you change that rule, it's not possible.</p><p>What about <strong>marketing text suggestions</strong>? It's the same logic.. Copilot will generate item descriptions by pulling from your current item attributes, and categories. If you do not fill out these fields or they are inconsistent, you will have a bad output.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://christian-schou.com/content/images/2026/02/business-central-copilot-generated-marketing-text.webp" class="kg-image" alt="business central, marketing text, ai generated content" loading="lazy" width="1598" height="756" srcset="https://christian-schou.com/content/images/size/w600/2026/02/business-central-copilot-generated-marketing-text.webp 600w, https://christian-schou.com/content/images/size/w1000/2026/02/business-central-copilot-generated-marketing-text.webp 1000w, https://christian-schou.com/content/images/2026/02/business-central-copilot-generated-marketing-text.webp 1598w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">AI-generated marketing text for a desk - source Microsoft</span></figcaption></figure><p><strong>The questions you should ask</strong>.</p><ol><li>When was the last time you made an audit of your master data?</li><li>Are your vendor and customer records up to date?</li><li>Is your chart of accounts structured in a way that makes sense today, or is it cluttered with bad data (garbage) from multiple years ago?</li><li>What about your item cards - are they filled out properly?</li></ol><h2 id="are-your-processes-consistent-enough-to-benefit-from-ai">Are Your Processes Consistent Enough to Benefit From AI?</h2><p>Microsoft differentiates between assistive and autonomous. Copilot's <strong>sales order line suggestions</strong> (the feature where it suggests items based on a customer's purchase history), and the <strong>e-document to purchase order mapping</strong> are both included in your Business Central license. These are both assistive features.</p><p>On the other hand we have the <strong>Sales Order Agent</strong> and the <strong>Payables Agent</strong>. These are agents on full auto that can interpret emails, handle attachments, and create sales or purchase documents with minimal input from a human.</p><p>Both agents are very cool (<em>if you ask me</em>), but they require something named <strong>Copilot Credits</strong>, which needs to be purchased separately through Microsoft Copilot Studio on a pay-as-you-go or pre-purchase basis.</p><p>If you would like to see at 20.000 feet how they work, here is an image from the official Microsoft Documentation.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://christian-schou.com/content/images/2026/02/copilot-business-central-sales-order-agent-general-flow.webp" class="kg-image" alt="sales order agent flow, business central" loading="lazy" width="1266" height="1376" srcset="https://christian-schou.com/content/images/size/w600/2026/02/copilot-business-central-sales-order-agent-general-flow.webp 600w, https://christian-schou.com/content/images/size/w1000/2026/02/copilot-business-central-sales-order-agent-general-flow.webp 1000w, https://christian-schou.com/content/images/2026/02/copilot-business-central-sales-order-agent-general-flow.webp 1266w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">General Flow, Sales Order Agent - Source: </span><a href="https://learn.microsoft.com/en-us/dynamics365/business-central/sales-order-agent?ref=christian-schou.com" rel="noreferrer"><span style="white-space: pre-wrap;">Microsoft</span></a></figcaption></figure><hr><p>Both assistive and autonomous are features that work best when you got some clear patterns they can learn from. Here are some of my top-points you should be aware of if using these:</p><ol><li>Consistent item usage.</li><li>Reliable vendor relationships.</li><li>Orders that follow a predictable structure.</li></ol><p>If you got a sales team working around the system or uses Business Central inconsistently, AI will have a tuff time providing any value. It's awesome at good processes, but it sucks at messy ones.</p><p><strong>The questions</strong> - Are your sales and purchasing workflows perfectly running through Business Central, or are people still relying on spreadsheets, emails, and some manual stuff? If autonomous agents are on your radar, do you have a clear picture of what that would cost and what it would take to configure?</p><h2 id="are-your-users-ready-to-work-with-ai">Are Your Users Ready to Work <em>With</em> AI?</h2><p>One thing I often hear is that AI will replace humans.. Come on, we have heard that story so many times... Every major technological leap like the printing press, industrial machinery, the computer, the internet, etc... triggered the same fear, and every time, it turned out to be more of a <strong>reshaping</strong> than a replacement.</p><p>Same thing goes for the Copilot features in Business Central. They are designed to assist, not replace humans and their ability to judge, and that distinction matters a lot in practice. Your bank reconciliation suggestions still need to be reviewed. Marketing copy still needs a human eye before going live. Order suggestions still need approval by humans.</p><p>If your team hasn't been introduced to how these features work, what they're based on, and when to push back on a suggestion, you risk either blind acceptance (<em>which defeats the purpose of human oversight</em>) or outright distrust to AI (<em>which means the feature goes unused entirely, which would be sad</em>).</p><p><strong>The questions</strong> - Have your finance, sales, and operations teams had any interaction with the Copilot features relevant to their roles yet? Do they understand what the AI is doing and why?</p><h2 id="so-where-does-that-leave-you">So, Where Does That Leave You?</h2><p>You made it to the end, thank you for reading this far. Are you confident at every point above? If yes, you may well be ready to start getting real value Copilot in Business Central starting today.</p><p>Hesitating? You are not the only one out there. It can be your deployment type, data quality, configurations, or simply the fact that your users are not ready for this yet. I would say that it won't be a reason to put Copilot on the "backburner", I think it is a good reason to be deliberate about how you will be approaching it.</p><p>Microsoft adding Copilot to Business Central is a big shift in how your ERP can work for your organisation. But like any other small or big change to your ERP, <strong>getting there well, is more valuable that getting there fast!</strong></p><p>If you got any gaps, fear not! Knowing where they are is the right place to start and make a plan for how you can roll out Copilot in your organisation.</p><h2 id="references">References</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.microsoft.com/en-us/dynamics-365/products/business-central/pricing?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Business Central Pricing | Microsoft Dynamics 365</div><div class="kg-bookmark-description">Discover flexible pricing options for Microsoft Dynamics 365 Business Central. Choose the right plan for your business and streamline your operations with ease.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-28.ico" alt=""><span class="kg-bookmark-author">Microsoft Dynamics 365</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/Solid-color-mobile-hero-BG" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dynamics365/business-central/copilot-overview?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">About Copilot in Business Central - Business Central</div><div class="kg-bookmark-description">This article answers common questions about Copilot in Business Central.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-29.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">MikeBC</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/open-graph-image-6.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dynamics365/release-plan/2025wave2/smb/dynamics365-business-central/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Overview of Dynamics 365 Business Central 2025 release wave 2</div><div class="kg-bookmark-description">Overview of Dynamics 365 Business Central 2025 release wave 2</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-30.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">relnotes</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/open-graph-image-7.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Extension Methods in Practice in C#]]></title>
                    <description><![CDATA[In my previous post (part one), I showed you the fundamentals of extension methods by extending IEnumerable&lt;T&gt;.

Understanding Extension Methods in C#One awesome feature I like about C# is the extension methods. Extension methods allow you to add new business logic to existing types without you]]></description>
                    <link>https://christian-schou.com/docs/advanced-csharp-features/extension-methods-in-practice-in-csharp/</link>
                    <guid isPermaLink="false">6988ec9e8114c6000127f8f9</guid>

                        <category><![CDATA[Advanced C# Features]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Tue, 10 Feb 2026 13:47:23 +0100</pubDate>


                    <content:encoded><![CDATA[<p>In my previous post (<em>part one</em>), I showed you the fundamentals of extension methods by extending <code>IEnumerable&lt;T&gt;</code>.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://christian-schou.com/collections/advanced-csharp-features/understanding-extension-methods-in-csharp/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Understanding Extension Methods in C#</div><div class="kg-bookmark-description">One awesome feature I like about C# is the extension methods. Extension methods allow you to add new business logic to existing types without you have to modify their original implementation or you have to create derived classes. I assume you know about LINQ and have used methods like Where(</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/cropped-cs-logo-color-retina-12.png" alt=""><span class="kg-bookmark-author">Tech with Christian</span><span class="kg-bookmark-publisher">Christian Schou Køster</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/twc-hero-banner-1-2.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>In this post I will show you a few things to be aware of that I have learned the hard way.</p><ul><li>What happens when extension methods share name with instance methods?</li><li>How does the compiler decide what code it should call?</li><li>What real-world patterns to use.</li></ul><p>I will show you a real-world example of a <code>Order</code> class, where we will add extension methods.</p><h2 id="a-simple-order-class">A Simple Order Class</h2><p>Most projects I have been involved in, has something to do with order management. You might have guessed it - let's start with a basic <code>Order</code> class that represents a customer order in a system.</p><pre><code class="language-csharp">namespace ExtensionMethod;

internal class Order
{
    public int OrderId { get; set; }
    public double TotalAmount { get; set; }
    public bool IsShipped { get; set; }
    public string CustomerName { get; set; }
    
    public Order(int orderId, double totalAmount, bool isShipped, string customerName)
    {
        OrderId = orderId;
        TotalAmount = totalAmount;
        IsShipped = isShipped;
        CustomerName = customerName;
    }
    
    public override string ToString() =&gt; 
        $"Order ID: {OrderId}, Customer: {CustomerName}, Total: {TotalAmount:C}, Shipped: {IsShipped}";
    
    public void ApplyDiscount(double flatDiscount)
    {
        TotalAmount -= flatDiscount;
    }
}</code></pre><p>Did your sharp eyes see the <code>ApplyDiscount(double flatDiscount)</code> and the <code>ToString()</code> methods? If not, read again 😄</p><h2 id="extending-the-order-class">Extending the Order Class</h2><p>Alright - let's extend the Order class with some useful extension methods.</p><pre><code class="language-csharp">internal static class OrderExtensions
{
    // Extension method to apply a percentage discount
    public static void ApplyDiscount(this Order order, double discountPercentage)
    {
        order.TotalAmount -= order.TotalAmount * (discountPercentage / 100);
    }
    
    // Extension method to check if the order is high-value
    public static bool IsHighValue(this Order order, double threshold)
    {
        return order.TotalAmount &gt; threshold;
    }
    
    // Extension method to mark an order as shipped
    public static void MarkAsShipped(this Order order)
    {
        order.IsShipped = true;
    }
}</code></pre><p>Confused? That was the intention 😅 We now got two methods named <code>ApplyDiscount</code>. What is the difference, Christian?!</p><ul><li>One instance method in the <code>Order</code> class (<em>takes a flat discount</em>)</li><li>One extension method in <code>OrderExtensions</code> (<em>takes a percentage</em>)</li></ul><p>How does this work? Let's find out.</p><h2 id="method-overloading-and-resolution-rules">Method Overloading and Resolution Rules</h2><p>This is where I messed up in the beginning of my career, and would like to teach you a thing, continue reading. Let's take a look at the usage of these methods.</p><pre><code class="language-csharp">public class OrderExtensionsDemo
{
    public static void Execute()
    {
        Order order = new(101, 1200.50, false, "Christian");
        Console.WriteLine(order);  // Initial Order
        
        // Apply a flat discount of $100 (Order Instance Method)
        order.ApplyDiscount(flatDiscount: 100);
        
        // Apply a 10% discount (Order Extension Method)
        order.ApplyDiscount(discountPercentage: 10);
        
        Console.WriteLine("After Discount:");
        Console.WriteLine(order);
        
        // Check if the order is high-value with a threshold of $1000
        bool isHighValue = order.IsHighValue(1000);
        Console.WriteLine($"Is High Value: {isHighValue}");
        
        // Mark the order as shipped
        order.MarkAsShipped();
        Console.WriteLine("After Marking Shipped:");
        Console.WriteLine(order);
    }
}</code></pre><p>So... why is the code above safe and works as intended? Named arguments save the day 🙈 Did you notice the use of named arguments in the discount calls I made?</p><pre><code class="language-csharp">order.ApplyDiscount(flatDiscount: 100);        // Calls instance method
order.ApplyDiscount(discountPercentage: 10);   // Calls extension method</code></pre><p>This saves our ass. By using named arguments, we can achieve</p><ol><li><strong>Disambiguate</strong> between methods with the same name.</li><li><strong>Self-documenting</strong> <strong>code</strong>—readers immediately understand the difference.</li><li><strong>Leverage method overloading</strong> between instance and extension methods.</li></ol><h2 id="method-resolution-priority">Method Resolution Priority</h2><p>The C# compiler follows these rules when resolving method calls - I had to go the hard way to find out, so here are the golden points.</p><ol><li><strong>Instance methods have priority.</strong> If an instance method matches the signature, it's chosen over an extension method.</li><li><strong>Extension methods are considered next.</strong> If no instance method matches, the compiler looks for extension methods.</li><li><strong>Closer namespaces win.</strong> Extension methods in the current namespace are preferred over those in imported namespaces. Did you know that?</li><li><strong>Ambiguity causes compilation errors.</strong> If multiple extension methods match equally well, you'll get a compiler error.</li></ol><h2 id="what-if-we-dont-use-named-arguments">What If We Don't Use Named Arguments?</h2><p>Without named arguments, this code would be ambiguous. If you were using an IDE, it would warn you about this because of the compiler.</p><pre><code class="language-csharp">order.ApplyDiscount(10);  // Which method?</code></pre><p>It's pretty simple, the compiler can't figure out what you want, since it has two options and both takes in the same parameter type.</p><ul><li>A $10 flat discount (<em>the instance method</em>)</li><li>A 10% discount (<em>the extension method</em>)</li></ul><p>Both methods takes in a <code>double</code> as parameter, so without named arguments, the call is ambiguous.</p><h2 id="running-the-example">Running the Example</h2><p>I have enabled debugging and placed a ton of break points in my code to trace what is happening. Let's break it down, and I will explain step-by-step what is happening.</p><pre><code class="language-text">Order ID: 101, Customer: Christian, Total: $1,200.50, Shipped: False</code></pre><p>After flat discount of $100, we will have the following result</p><pre><code class="language-text">Total: $1,100.50</code></pre><p>After 10% discount, the value will be</p><pre><code class="language-text">Total: $990.45  // $1,100.50 - ($1,100.50 * 0.10)</code></pre><p>After checking high-value status, we got</p><pre><code class="language-text">Is High Value: False  // $990.45 is not &gt; $1,000</code></pre><p>After marking it as shipped, we will have the following</p><pre><code class="language-text">Order ID: 101, Customer: Christian, Total: $990.45, Shipped: True</code></pre><h2 id="real-world-patterns-and-best-practices">Real-World Patterns and Best Practices</h2><p>I have learned a lot, and also the hard way. Below are some of my best practices I have learned during the years of development and making architecture. You can skip them, read them and pick what you think works for you.</p><h3 id="use-extension-methods-to-separate-concerns">Use Extension Methods to Separate Concerns</h3><p>I have come to the conclusion that extension methods are perfect for adding functionality without bloating your core classes.</p><pre><code class="language-csharp">// Core class stays focused on data and essential operations
class Order { /* ... */ }

// Business logic extensions
static class OrderBusinessExtensions { /* ... */ }

// Validation extensions
static class OrderValidationExtensions { /* ... */ }

// Formatting extensions
static class OrderFormattingExtensions { /* ... */ }</code></pre><p>Why do this? It gives us the option to keep our classes focused and makes it easier to test different aspects of our codebase independently. What's not to like!?</p><h3 id="consider-mutability-carefully">Consider Mutability Carefully</h3><p>Have you noticed that my extension methods mutate the <code>Order</code> object?</p><pre><code class="language-csharp">public static void MarkAsShipped(this Order order)
{
    order.IsShipped = true;  // Mutating state
}</code></pre><p>Why does that work? It's simple - the <code>Order</code> is a reference type. But I would strongly encourage you to consider whether you want your extension methods to mutate the state or return a new instance, like here.</p><pre><code class="language-csharp">// Mutating (current approach)
order.ApplyDiscount(10);

// Immutable alternative
var discountedOrder = order.WithDiscount(10);</code></pre><p>The only thing you have to be aware of is that for value types (<strong><em>structs</em></strong>), mutation in extension methods will not work as you expect, without you are using the <code>ref</code> keyword, as I am showing here.</p><pre><code class="language-csharp">public static void ExtensionMethod(ref this MyStruct s)  // Note the ref
{
    s.Value = 42;
}</code></pre><h3 id="use-meaningful-names-and-avoid-conflicts">Use Meaningful Names and Avoid Conflicts</h3><p>When I am naming my extension methods, I have some rules.</p><ul><li><strong>Always be specific</strong>. <code>MarkAsShipped()</code> is better than <code>Ship()</code>.</li><li><strong>Follow conventions</strong>. If it returns <code>bool</code>, consider starting with <code>Is</code>, <code>Has</code>, or <code>Can</code>. It makes the codebase easier to read and maintain in the future.</li><li><strong>Avoid collision with future framework methods</strong>. Don't create extensions that might conflict with future .NET versions. I know this is a hard to know about, but try to stay out of their framework.</li></ul><p>Let’s say you add an extension method to a very common .NET type, like here.</p><pre><code class="language-csharp">public static class StringExtensions
{
    public static bool IsNullOrEmpty(this string value)
    {
        return string.IsNullOrEmpty(value);
    }
}
</code></pre><p>This <strong>works today</strong>, but it’s risky! But why?</p><ul><li><code>string.IsNullOrEmpty</code> already exists as a <strong>static</strong> method.</li><li>.NET <em>could</em> add an <strong>instance method</strong> called <code>IsNullOrEmpty()</code> in a future version.</li><li>If they do, your extension method either<ul><li>Stops being called.</li><li>Causes ambiguity errors.</li><li>Or behaves differently across .NET versions.</li></ul></li></ul><p>That’s a collision with the framework.</p><h3 id="consider-discoverability">Consider Discoverability</h3><p>Extension methods are only available when the namespace is imported. That is why I always try to group related extension into one namespace, and disable the pragma warning in the IDE for it.</p><pre><code class="language-csharp">using ExtensionMethod;  // Now OrderExtensions are available</code></pre><p>Group related extensions in the same namespaces, will help you and other developers discover them.</p><h3 id="document-your-parameter-expectations">Document Your Parameter Expectations</h3><p>You should always use named parameters when it improves clarity of your extension method. Like shown here.</p><pre><code class="language-csharp">// Clear intent
order.IsHighValue(threshold: 1000);

// Less clear
order.IsHighValue(1000);  // 1000 what?</code></pre><h2 id="when-to-use-extension-methods-vs-instance-methods">When to Use Extension Methods vs. Instance Methods?</h2><p>Use Instance Methods When</p><ul><li>✅ The functionality is core to the type's purpose.</li><li>✅ The method needs access to private members.</li><li>✅ The method is part of the type's contract/interface.</li><li>✅ Polymorphism is required (<em>virtual/override</em>).</li></ul><p>Use Extension Methods When</p><ul><li>✅ You don't own the type (<em>framework types, third-party libraries</em>).</li><li>✅ The functionality is supplementary or domain-specific.</li><li>✅ You want to separate concerns across different assemblies.</li><li>✅ You're creating fluent APIs or query helpers.</li><li>✅ You want to avoid bloating a core class.</li></ul><h2 id="extension-method-chains">Extension Method Chains</h2><p>Remember i mentioned something about writing code that looks like a nice API? This is what chaining allows us to do. Imagine being able to do this:</p><pre><code class="language-csharp">var processedOrder = order
    .ApplyDiscount(discountPercentage: 15)
    .MarkAsShipped()
    .GenerateInvoice()
    .SendConfirmation();</code></pre><p>For this to work, each extension method needs to return the order (<em>or a modified version</em>).</p><pre><code class="language-csharp">public static Order MarkAsShipped(this Order order)
{
    order.IsShipped = true;
    return order;  // Enable chaining
}</code></pre><h2 id="key-takeaways">Key Takeaways</h2><p>As always, I have some key takeaways 😄</p><ol><li><strong>Instance methods always win</strong>. They have priority over extension methods in method resolution, that's just how it is.</li><li><strong>Named arguments disambiguate</strong>. Use them when you have overloaded instance and extension methods.</li><li><strong>Extension methods enable separation of concerns</strong>. Keep core classes focused, extend with specialized functionality. This also promotes testability of your code.</li><li><strong>Be mindful of mutability</strong>. Decide whether your extensions should mutate or return new instances.</li><li><strong>Namespace organization matters</strong>. Group related extensions and use clear naming conventions, that makes it easier to discover all extensions for an object.</li><li><strong>Extension methods can't access private members</strong>. They operate on the public surface of a type. I did not mention that earlier, but now you know.</li></ol><h2 id="summary">Summary</h2><p>An extension method is a very powerful tool in the developer toolbox. It gives us the option to write clean, and maintainable code. With extensions we can easily extend both built-in framework types and our own classes without violating the Single Responsibility Principle (from SOLID).</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://christian-schou.com/blog/what-are-the-solid-principles/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">SOLID Principles - The art of writing maintainable code 👨‍💻</div><div class="kg-bookmark-description">Learn what the SOLID principles are about and how you can use them to write clean and maintainable code in your applications. With C# examples.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/cropped-cs-logo-color-retina-13.png" alt=""><span class="kg-bookmark-author">Tech with Christian</span><span class="kg-bookmark-publisher">Christian Schou Køster</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/solid-principles-1-1.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>The big key is to use them when needed. Only create extensions when it makes sense and keep your method names clean and clear. Always go for named arguments when you got an ambiguity.</p><p>If you combine extension methods with LINQ, generics and other awesome C# features, your extension methods will become cornerstones. If you enjoyed this post, let me know in the comments. Until next time - happy coding! ✌️</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Understanding Extension Methods in C#]]></title>
                    <description><![CDATA[One awesome feature I like about C# is the extension methods. Extension methods allow you to add new business logic to existing types without you have to modify their original implementation or you have to create derived classes.

I assume you know about LINQ and have used methods like Where(]]></description>
                    <link>https://christian-schou.com/docs/advanced-csharp-features/understanding-extension-methods-in-csharp/</link>
                    <guid isPermaLink="false">6988e0288114c6000127f8d4</guid>

                        <category><![CDATA[Advanced C# Features]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Sun, 08 Feb 2026 20:13:47 +0100</pubDate>


                    <content:encoded><![CDATA[<p>One awesome feature I like about C# is the extension methods. Extension methods allow you to add new business logic to existing types without you have to modify their original implementation or you have to create derived classes.</p><p>I assume you know about <strong>LINQ</strong> and have used methods like <code>Where()</code>, <code>Select()</code>, or <code>FirstOrDefault()</code>? Do you know what they really are? Extension methods! 👌</p><p>In this collection post, I will show you how extension methods work, why they are handy, and how you can create your own to extend your own classes or framework types like e.g. <code>IEnumerable&lt;T&gt;</code>.</p><h2 id="what-are-extension-methods">What Are Extension Methods?</h2><p>As I just mentioned, an extension method makes it possible to "add" methods to an existing type. In your IDE at development time, they appear as instance methods on the type you extend, but really they are just static methods you define in a static class.</p><p>I often refer to this as syntactic sugar - why? It makes our code more readable, and enable you and others to write code in a fluent, and "API" friendly design.</p><p>An extension method must always:</p><ol><li>Be defined in a static class.</li><li>Be a static method.</li><li>Have <code>this</code> keyword along with the first parameter. This tells us about the type being extended.</li></ol><p>Alright - let's get to the fun part, and see it in action 🔥</p><h2 id="building-a-custom-linq-style-method">Building a Custom LINQ-Style Method</h2><p>Let's start soft by extending <code>IEnumerable&lt;T&gt;</code> with a custom find operation. Here is the code - I will explain below.</p><pre><code class="language-csharp">namespace ExtensionMethod;

internal static class IEnumerableExtensions
{
    public static IEnumerable&lt;T&gt; CustomFind&lt;T&gt;(this IEnumerable&lt;T&gt; source, Func&lt;T, bool&gt; isMatch)
    {
        foreach (var item in source)
        {
            if (isMatch(item))
            {
                yield return item;
            }
        }
    }
}</code></pre><p>Okay, let me break it down and tell you what is happening above.</p><h3 id="the-method-signature">The Method Signature</h3><pre><code class="language-csharp">public static IEnumerable&lt;T&gt; CustomFind&lt;T&gt;(this IEnumerable&lt;T&gt; source, Func&lt;T, bool&gt; isMatch)</code></pre><ul><li><code><strong>this IEnumerable&lt;T&gt; source</strong></code>. The <code>this</code> keyword makes this an extension method on <code>IEnumerable&lt;T&gt;</code>. Any collection implementing this interface can now call <code>CustomFind()</code>.</li><li><strong>Generic Type <code>&lt;T&gt;</code></strong>. Makes the method work with any type. Awesome part about that is it provides type safety 🙌</li><li><strong><code>Func&lt;T, bool&gt; isMatch</code></strong>. A predicate function that figures out whether an item matches our criteria or not.</li></ul><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">💡</div><div class="kg-callout-text">A predicate function is a function that returns a boolean value (<i><em class="italic" style="white-space: pre-wrap;">true or false</em></i>). It evaluates a condition and tells you whether something meets a certain criteria.</div></div><h3 id="deferred-execution-with-yield-return">Deferred Execution with <code>yield return</code></h3><p>Do you care about performance? I do, and that is why I am using <code>yield return</code> inside the method, it is actually crucial when talking performance if you ask me.</p><pre><code class="language-csharp">foreach (var item in source)
{
    if (isMatch(item))
    {
        yield return item;
    }
}</code></pre><p>Why is <code>yield return</code> making it perform better? The reason is because it creates an iterator that provides deferred execution. Christian... what the h... does that mean? Let me try to explain that for you.</p><ul><li>The method doesn't process the entire collection instantly/immediately.</li><li>Items are evaluated one at a time, and only when requested.</li><li>If you only need the first matching item, you won't iterate through the entire collection of items.</li><li>Multiple operations can be chained without creating intermediate collections.</li></ul><p>The exact same pattern is actually used by LINQ methods like <code>Where()</code>, <code>Select()</code>, and <code>Take()</code>. You can check out their source implementation at the link below if you are curious.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/dotnet/runtime/tree/main/src/libraries/System.Linq?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">runtime/src/libraries/System.Linq at main · dotnet/runtime</div><div class="kg-bookmark-description">.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps. - dotnet/runtime</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/pinned-octocat-093da3e6fa40-7.svg" alt=""><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">dotnet</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/runtime" alt="" onerror="this.style.display = 'none'"></div></a></figure><h3 id="using-the-extension-method">Using the Extension Method</h3><p>Now that we have the extension method and we know how any why it works, let's try using it in some practical code.</p><pre><code class="language-csharp">public class IEnumerableExtensionsDemo
{
    public static void Execute()
    {
        List&lt;int&gt; numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        
        // Find all even numbers
        var evenNumbers = numbers.CustomFind(n =&gt; n % 2 == 0);
        Console.WriteLine("Even Numbers:");
        foreach (var number in evenNumbers)
        {
            Console.WriteLine(number);
        }
        
        // Find all numbers greater than 5
        var greaterThanFive = numbers.CustomFind(n =&gt; n &gt; 5);
        Console.WriteLine("Numbers Greater Than 5:");
        foreach (var number in greaterThanFive)
        {
            Console.WriteLine(number);
        }
    }
}</code></pre><p>Can you see it now? Our extension method looks like it is a built-in method in the LINQ framework, but on the <code>List&lt;int&gt;</code> variable. Why does this work? It works because <code>List&lt;int&gt;</code> implements <code>IEnumerable&lt;int&gt;</code>, this makes our extension available automatically.</p><p>The result?</p><pre><code class="language-text">Even Numbers:
2
4
6
8
10
Numbers Greater Than 5:
6
7
8
9
10</code></pre><h2 id="why-not-just-use-linqs-where">Why Not Just Use LINQ's <code>Where()</code>?</h2><p>I know what you might be thinking. Why do that? Why not use the <code>Where()</code> method? and you are absolutely right! In a real-world solution, you would just stick to the LINQ method for solving that.</p><p>This was a demonstration, and with the code above, I was able to demonstrate the following:</p><ol><li><strong>How LINQ actually works under the hood.</strong> Many LINQ methods are extension methods using similar patterns.</li><li><strong>How to create domain-specific extensions.</strong> You can build custom query methods tailored to your business logic. Cool right!?</li><li><strong>The power of extending framework types.</strong> You're not limited to what Microsoft provides, the sky is the limit.</li></ol><h2 id="when-to-create-extension-methods-on-framework-types">When to Create Extension Methods on Framework Types?</h2><p>With great power comes great responsibility, and extension methods on framework types are powerful, let me tell you that, and it is also the reason why you should be careful.</p><p>Below are some of my key takeaways for using them and avoiding them in my projects. You are welcome to follow that route or just stick to your own.</p><p>✅ I use them when I</p><ul><li>Create domain-specific query operations that compose well with LINQ.</li><li>Add utility methods that would benefit from a fluent syntax - we are back to the "API-like" design here.</li><li>Fill gaps in framework functionality for my specific needs in a solution.</li><li>Have to create more readable alternatives to static utility methods.</li></ul><p>❌ Avoid them when</p><ul><li>The functionality is too specific to one use case.</li><li>They would confuse other developers (<em>especially naming conflicts with future framework additions</em>)</li><li>A simple static helper method would be clearer for other developers.</li></ul><h2 id="key-takeaways">Key Takeaways</h2><ul><li><strong>Extension methods are syntactic sugar.</strong> They're static methods that appear as instance methods.</li><li><strong>Use <code>this</code> on the first parameter.</strong> This tells us the type being extended.</li><li><strong>Deferred execution matters.</strong> Use <code>yield return</code> for collection operations to maintain performance. ALWAYS!</li><li><strong>Generic extension methods are powerful.</strong> They work across all types that match the constraint.</li><li><strong>Namespace matters</strong>: You need to use the <code>using</code> namespace containing the extension class to access the methods. I often practice placing them in a global file for the project or placing them in a root namespace (if it makes sense - often the case in a class library).</li></ul><h2 id="end-of-part-one">End of part one</h2><p>The end of the first post in this section. You should now have a great foundational knowledge about extension methods. By now I hope you have the idea that they make it possible for you to write expressive fluent code by extending on types.</p><p>When you learn to use them correctly, they can take your codebase to the next level and make it more like an API. I really enjoy reviewing code written in that style! Turn it into art, please 🙏</p><p>In the next and final post in this section, I will show you some cool and a bit more advanced scenarios. We will also take a look at what happens when an extension method clashes with instance methods, how the resolution of them works, and again some practical examples for how you can extend your own classes.</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Extend your Extensions in .NET]]></title>
                    <description><![CDATA[Starting from .NET 10 we will get new features for extensions. We will now get extension members that will give us the option for extending our extensions 🔥]]></description>
                    <link>https://christian-schou.com/blog/extend-your-extensions-in-net/</link>
                    <guid isPermaLink="false">68e62dc9214bae0001e005e0</guid>

                        <category><![CDATA[.NET]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Mon, 24 Nov 2025 13:56:38 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2025/11/extend-extensions-in-dotnet-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2025/11/extend-extensions-in-dotnet-twc.webp" alt="Extend your Extensions in .NET"/> <p>When .NET 9 was released we got extension members. I am sure we have only seen the beginning of them, as there is a lot of potential in that (if you ask me). They are a way for developers to improve existing types.</p><p>To make it easier to understand what I mean, I have prepared a simple example for you:</p><pre><code class="language-csharp">// This will be possible in .NET 10
extension StringExtensions for string
{
    public int WordCount =&gt; this.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}

// Using it
string sentence = "This is a test";
int count = sentence.WordCount;  // 4</code></pre><p>See the awesome part about this? They will give you a feeling that the extension is at language-level. It's not just a static method you added as a workaround. With the extension members we can easily add new methods or properties to our existing types.</p><div class="kg-card kg-callout-card kg-callout-card-accent"><div class="kg-callout-emoji">💡</div><div class="kg-callout-text">You can actually define private members (<i><em class="italic" style="white-space: pre-wrap;">methods or other properties</em></i>) in the extension container. The only issue (<i><em class="italic" style="white-space: pre-wrap;">right now</em></i>) is that private members doesn't serve that much of a purpose at the moment.</div></div><h2 id="%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB-a-practical-example">🧑‍💻 A Practical Example</h2><p>Let's make a practical example to see how we can utilize this in a real world. I have created a set of classes for your reference.</p><pre><code class="language-csharp">public abstract class Vehicle
{
    public string Make { get; set; }
    public string Model { get; set; }

    public sealed class Car : Vehicle
    {
        public int NumberOfDoors { get; set; }
        public bool IsElectric { get; set; }
    }

    public sealed class Truck : Vehicle
    {
        public double PayloadCapacity { get; set; }
        public bool HasFourWheelDrive { get; set; }
    }

    public sealed class Motorcycle : Vehicle
    {
        public bool HasSidecar { get; set; }
        public string EngineType { get; set; }
    }
}
</code></pre><p>Imagine you would like to perform some sort of validation on the truck for e.g. the payload capacity. That would end up in something similar to this code:</p><pre><code class="language-csharp">var truck = new Vehicle.Truck();

if (truck is Vehicle.Truck t)
{
    if (t.PayloadCapacity &lt;= 0)
    {
        // Handle invalid payload (e.g., throw, set default, etc.)
    }
    else
    {
        // Payload is OK
    }
}
</code></pre><p>This becomes a bit cluttered, what if you could do all of that in an extension using the new extension members being available from .NET 10?</p><pre><code class="language-csharp">public static class VehicleExtensions
{
    extension(Vehicle.Truck truck)
    {
        public bool HasValidPayload =&gt; truck.PayloadCapacity &gt; 0;
    }
}
</code></pre><p>This provides us with an extension for the Truck class now where the validation is abstracted away from the condition we made before. Let's use it to validate the payload.</p><pre><code class="language-csharp">var truck = new Vehicle.Truck { PayloadCapacity = 5.0 };

if (truck.HasValidPayload)
{
    Console.WriteLine("Truck payload capacity is valid.");
}
else
{
    Console.WriteLine("Truck payload capacity is invalid!");
}
</code></pre><p>See how clean my conditional logic became after doing that? It's now fully readable 💪 One thing I really like about this is that we now get the option to extend static classes and that is pretty awesome! 🔥</p><p>But it doesn't stop here, there is much more we can do. Below are a few examples to showcase it.</p><h2 id="%F0%9F%A7%B1-extending-enums">🧱 Extending Enums</h2><p>Enums can be extended as well. When I first read that I was curious about what was possible. One thin I often see written over and over again are parsers for enums.</p><p>Yes... I have done a few myself in various projects, but this can be handled pretty beautiful with an extension.</p><p>Suppose you have a <code>VehicleType</code> enum.</p><pre><code class="language-csharp">public enum VehicleType
{
    Car,
    Truck,
    Motorcycle
}</code></pre><p>Let's write an extension to parse a string to the enum member.</p><pre><code class="language-csharp">public static class VehicleTypeExtensions
{
    extension&lt;T&gt;(T) where T : struct, Enum
    {
        public static T Parse(string value) =&gt;
            Enum.Parse&lt;T&gt;(value);
    }
}</code></pre><p>And applied in the business logic, it ends up like this.</p><pre><code class="language-csharp">var type = VehicleType.Parse("Truck");
Console.WriteLine(type);  // Output: Truck</code></pre><p>That is how easy it is. I know that example is pretty simple, but it is also just to showcase what is possible.</p><p>What about null or empty values? Something we hate as developers 😅</p><h2 id="%F0%9F%9A%A8-null-and-empty-checks">🚨 Null and Empty Checks</h2><p>Image you have a <code>List&lt;string&gt;</code> representing installed parts on a vehicle, and you want to ensure the list isn’t empty. How could we easily accomplish that?</p><p>I am so glad you asked... an extension 😆 Let's see how a null/empty check could be implemented using extensions.</p><pre><code class="language-csharp">public static class VehicleGuardExtensions
{
    extension(ArgumentException)
    {
        public static void ThrowIfPartsListEmpty(IList&lt;string&gt; parts)
        {
            if (parts.Count == 0)
            {
                throw new ArgumentException("Vehicle must have at least one part installed.", nameof(parts));
            }
        }
    }
}</code></pre><p>The extension above will throw an argument exception if the requirement about minimum installed parts are not met. Using it will be as easy as this.</p><pre><code class="language-csharp">var parts = new List&lt;string&gt;();
ArgumentException.ThrowIfPartsListEmpty(parts);</code></pre><p>Can you see the idea of doing that? Now we are using the base exception for arguments, but with our own logic behind a saying name making the code super readable for anybody.</p><p>I can say that I am already planning to implement this in a lot of the business applications where I got the design responsibility.</p><p>Let's have a look at one last thing, and call it.</p><h2 id="%F0%9F%94%84-static-extension-for-factory-defaults">🔄 Static Extension for Factory Defaults</h2><p>If you find yourself having default values for a specific kind of your type, e.g. a truck, you could add an extension for creating just that. Let me show you how.</p><pre><code class="language-csharp">public static class VehicleFactoryExtensions
{
    extension(Vehicle)
    {
        public static Vehicle.Truck CreateDefaultTruck() =&gt;
            new Vehicle.Truck
            {
                Make = "Generic",
                Model = "Hauler",
                PayloadCapacity = 2.0,
                HasFourWheelDrive = true
            };
    }
}</code></pre><p>You can see this as a kind of a template, where you define what a default truck is. When applied in business logic, it will look like this.</p><pre><code class="language-csharp">var defaultTruck = Vehicle.CreateDefaultTruck();
Console.WriteLine(defaultTruck.Model);  // Output: Hauler</code></pre><h2 id="%F0%9F%92%AD-some-thoughts">💭 Some Thoughts</h2><p>I really like the extension members feature! They are a really cool way of making the code clean without having to re-invent the wheel. If you ask me, I think they are more powerfull and expressive at the same time, compared to the traditional extension methods we are building.</p><p>My example above was focused on a Vehicle, but it could literally be anything this could be applied to like types (the Vehicle) or static members like enums and factories.</p><p>They make our code more readable and when implement in a full codebase, your domain logic will start feeling like it is a natural part of the language. Can it be any better?</p><p>Behind all the awesome stuff I just showed and mentioned, there are always some things that could be made better, and hopefully they will in the future, who knows? To mention a few things:</p><h3 id="%E2%9D%8C-no-fields-in-extension-containers">❌ No Fields in Extension Containers</h3><p>At the moment you cannot define fields inside an extension contianer. Only properties and methods are allowed. That limits a few optimizations or a shared internal state in the extension.</p><h3 id="%E2%9D%8C-no-private-reuse-across-extension-members">❌ No Private Reuse Across Extension Members</h3><p>There is no option for reusing private computed properties or logic inside the same extension container. Here is an example:</p><pre><code class="language-csharp">extension(Vehicle.Truck truck)
{
    private bool IsOverloaded =&gt; truck.PayloadCapacity &gt; 10;

    // ❌ Error: 'IsOverloaded' does not exist in this context
    public bool ShouldUpgradeSuspension =&gt; IsOverloaded;
}
</code></pre><p>This is currently not allowed, but would really be a nice feature! It could help improve the readability of the logic and encapsulate the shared logic inside the extension container.</p><h3 id="%F0%9F%94%90-no-instance-field-access-from-extension">🔐 No Instance Field Access from Extension</h3><p>Let's say I wanted to give all my vehicles an internal field that only extensions could see. This could be for diagnostics or telemetry (<em>thoughts on top of my head</em>). If the .NET team implemented that, we might get something similar to this:</p><pre><code class="language-csharp">public abstract class Vehicle
{
    internal extension string _diagnosticsId;
}

extension(Vehicle vehicle)
{
    private string? DiagnosticsId =&gt; vehicle._diagnosticsId;
}
</code></pre><p>Why would I like that feature? Depends on your project, but I foresee that it could open the option for using patterns to accomplish <a href="https://en.wikipedia.org/wiki/Metaprogramming?ref=christian-schou.com" rel="noreferrer">metaprogramming</a> or do diagnostics at runtime. I am currently building a modular monolith and I really think that this would be great in such a system.</p><div class="kg-card kg-callout-card kg-callout-card-accent"><div class="kg-callout-emoji">🤓</div><div class="kg-callout-text">Metaprogramming is an advanced concept where your code can inspect, modify, or generate other code at runtime or compile time 🔥 don't get scared 😁 I have personally used it for automating stuff. I once build a flexible API that was generated on the fly and even used it for injecting configurations in PLCs dynamically.</div></div><p>So, what do you think? 😅 How would <strong>you</strong> use extension members in your codebase? Let me know your thoughts in the comments! Until next time - happy programming! ✌️</p><h2 id="%F0%9F%93%9A-references">📚 References</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://en.wikipedia.org/wiki/Metaprogramming?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Metaprogramming - Wikipedia</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/wikipedia.png" alt=""><span class="kg-bookmark-author">Wikimedia Foundation, Inc.</span><span class="kg-bookmark-publisher">Contributors to Wikimedia projects</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/40px-Ambox_important.svg.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Extension members - C#</div><div class="kg-bookmark-description">Extension members in C# enable you to add methods, properties, or operators to existing types without creating a new derived type, recompiling, or otherwise modifying the original type.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-21.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">BillWagner</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/logo_csharp-2.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extension?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Extension member declarations - C# reference</div><div class="kg-bookmark-description">Learn the syntax to declare extension members in C#. Extension members enable you to add functionality to types and interfaces in those instances where you don’t have the source for the original type. Extensions are often paired with generic interfaces to implement a common set of functionality across all types that implement that interface.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-22.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">BillWagner</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/logo_csharp-3.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Serilog]]></title>
                    <description><![CDATA[Makes logging in .NET feel effortless. You can send logs to the console, files, or just about anywhere, but the real magic is its structured logging.]]></description>
                    <link>https://christian-schou.com/nugets/serilog/</link>
                    <guid isPermaLink="false">69206e19214bae0001e009d5</guid>


                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Fri, 21 Nov 2025 14:52:35 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2025/11/serilog-nuget.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2025/11/serilog-nuget.webp" alt="Serilog"/> <p>Serilog is a very popular library for .NET when it comes to logging. It will give you a nice, and flexible way to log events in your application. If you are looking for my tutorials/posts about Serilog, they are just below. If you are only checking it out, just continue and read along. 🙌</p><h2 id="tutorials">Tutorials</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://christian-schou.com/blog/use-serilog-with-asp-net-core-net6/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">How to Add logging to ASP.NET Core using Serilog - .NET6</div><div class="kg-bookmark-description">Add Serilog to start logging to ASP.NET Core using Serilog with this easy step-by-step .NET Tutorial about logging using Serilog in .NET6.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/cropped-cs-logo-color-retina-11.png" alt=""><span class="kg-bookmark-author">Tech with Christian</span><span class="kg-bookmark-publisher">Christian Schou Køster</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/Serilog-Logging-Asp-Net-Core-Christian-Schou-optimized.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><h2 id="key-features-of-serilog">Key features of Serilog</h2><p>Below is a list that makes up some key features of Serilog (<em>my personal opinion</em>).</p><h3 id="structured-logging">Structured logging</h3><p>A normal logger is only writing text strings, and that is perfect as that is what logs really are. With Serilog you can do the same, but this time as structured data. This will give you a new level of options when it comes to analyse your logs. Why? Because Serilog can log objects and properties that you can query. 🙌 Wanna see how easy it is?</p><pre><code class="language-csharp">var user = new { Id = 123, Username = "john_doe", Email = "john@example.com" };
var orderTotal = 450.75m;

// The object properties are captured as structured data
Log.Information("User {@User} placed order for {OrderTotal:C}", user, orderTotal);

// Output includes the full user object structure, not just "User.ToString()"
// OrderTotal is formatted as currency</code></pre><p>The <code>@</code> operator destructures the object, and <code>:C</code> is a format specifier. Imagine that you can query logs like "<em>show me all orders over $1000 that failed</em>" is incredibly powerful for debugging production issues. 🔥</p><h3 id="sinks">Sinks</h3><p>With the many available sinks for Serilog, you can easily make it possible to "ship" your logs to multiple destinations at the same time. This could be:</p><ul><li>The console.</li><li>A single- or rolling file.</li><li>A database.</li><li>A cloud service like:<ul><li><a href="https://azure.microsoft.com/da-dk/products/monitor?ref=christian-schou.com" rel="noreferrer">Azure Monitor</a>.</li><li><a href="https://datalust.co/?ref=christian-schou.com" rel="noreferrer">Seq</a> by Datalust.</li><li>etc...</li></ul></li></ul><h3 id="message-templates">Message templates</h3><p>A feature that I really like are the templating syntax for separating my message from the data I am feeding to it. Here is an example.</p><pre><code class="language-csharp">Log.Information("User {Username} logged in from {IpAddress}", username, ipAddress);</code></pre><h3 id="minimum-level-filtering">Minimum level filtering</h3><p>When developing the application I enjoy having my environment in either verbose or debug as that provides me a lot of logs and I can easily trace what is going on in the application.</p><p>With Serilog you can easily configure which log levels are captured at runtime. This can vary from <code>Verbose</code>, <code>Debug</code>, <code>Information</code>, <code>Warning</code>, <code>Error</code>, and up to <code>Fatal</code>.</p><h3 id="easy-integration">Easy integration</h3><p>If your applications are already using the built-in logging and DI (dependency injection), you can easily "enable" Serilog. It can be used in any .NET application and works well with the built-int logging system.</p><p>No need for rewriting your whole application, which also makes a great selling point. I am personally using Serilog in console apps, APIs, worker services, and much more.</p><h2 id="my-favorite-things-about-serilog">My favorite things about Serilog</h2><p>Below is a list with some of the coolest things I think that Serilog provides. If you got any suggestions that I should add, please let me know in the comments! ✌️</p><h3 id="a-super-simple-basic-setup">A super simple basic setup</h3><p>All you have to do is install the Serilog package, and add the following code to your <code>Program.cs</code> file.</p><pre><code class="language-csharp">using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

Log.Information("Application starting up");</code></pre><h3 id="multiple-sinks">Multiple Sinks</h3><p>In the market for a solution that can ship your logs to multiple places at the same time? This is how you can do it with Serilog.</p><pre><code class="language-csharp">Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("logs/app.txt")
    .WriteTo.Seq("http://localhost:5341")
    .WriteTo.ApplicationInsights(telemetryConfiguration, TelemetryConverter.Traces)
    .CreateLogger();</code></pre><h3 id="enrichers">Enrichers</h3><p>If you would like to automatically add context to your logs, this can easily be done using the configuration. Here is an example where I am adding <code>machine name</code>, <code>thread ID</code>, and <code>application name</code> <strong>to all logs</strong>.</p><pre><code class="language-csharp">Log.Logger = new LoggerConfiguration()
    .Enrich.WithMachineName()
    .Enrich.WithThreadId()
    .Enrich.WithProperty("Application", "MyApp")
    .Enrich.FromLogContext() // Important for correlation IDs
    .WriteTo.Console()
    .CreateLogger();</code></pre><h3 id="log-context-with-scoped-properties">Log context with scoped properties</h3><p>You can create a scope and include necessary details within that scope when the logs are created. Here is an example where I make sure that everything logged within this scope will include <code>OrderId</code>.</p><pre><code class="language-csharp">using Serilog.Context;

public async Task ProcessOrder(int orderId)
{
    // All these logs will have OrderId attached
    using (LogContext.PushProperty("OrderId", orderId))
    {
        Log.Information("Starting order processing");
        await ValidateOrder();
        await ChargePayment();
        Log.Information("Order processing complete");
    }
}</code></pre><h3 id="conditional-logging-filters">Conditional Logging &amp; Filters</h3><p>Tired of logs from some providers? With the option for conditional logging and filters in Serilog, we can setup the configuration to suppress noise 😅</p><pre><code class="language-csharp">Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) // Suppress noisy MS logs
    .MinimumLevel.Override("System", LogEventLevel.Warning)
    .Filter.ByExcluding(logEvent =&gt; 
        logEvent.Properties.ContainsKey("RequestPath") &amp;&amp; 
        logEvent.Properties["RequestPath"].ToString().Contains("/health"))
    .WriteTo.Console()
    .CreateLogger();</code></pre><p>In the example above, I am suppressing Microsoft logs in the application.</p><h3 id="sub-loggers-for-different-components">Sub-loggers for Different Components</h3><p>This is cool! Especially if you got something very specific like a component in your application and you would like to show exactly what class they come from. This is how you can do it.</p><pre><code class="language-csharp">public class OrderService
{
    private readonly ILogger _logger;

    public OrderService()
    {
        // Creates a logger specifically for this class
        _logger = Log.ForContext&lt;OrderService&gt;();
        // or
        _logger = Log.ForContext("SourceContext", "Orders.Processing");
    }

    public void ProcessOrder()
    {
        _logger.Information("Processing order");
    }
}</code></pre><p>Logs will show they came from the <code>OrderService</code>.</p><h3 id="exception-logging">Exception Logging</h3><p>It's easy to log your exceptions. Serilog can log our full exceptions with the stack trace 🔥</p><pre><code class="language-csharp">try
{
    // risky operation ;)
    ProcessPayment();
}
catch (Exception ex)
{
    Log.Error(ex, "Payment processing failed for order {OrderId}", orderId);
    throw;
}</code></pre><h3 id="configuration-from-appsettingsjson">Configuration from appsettings.json</h3><p>No need for hardcoding the configuration. I am often working with containers and I really want to have the option to configure the containers from environment variables. This is of course also an option in Serilog, as we can configure it from <code>appsettings.json</code>.</p><pre><code class="language-json">{
  "Serilog": {
    "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": {
          "path": "logs/log-.txt",
          "rollingInterval": "Day"
        }
      }
    ],
    "Enrich": ["FromLogContext", "WithMachineName"]
  }
}</code></pre><h2 id="the-end">The End</h2><p>Serilog is my go-to for logging. I think it is one of the most adopted logging frameworks in the .NET ecosystem, and I think it is because of its great performance, flexibility, and many options it provides like sinks and enrichers making it easy to query logs in a structured way.</p><p>If you got any suggestions for this page, please let me know in the comments. Happy logging! ✌️</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Newtonsoft.Json]]></title>
                    <description><![CDATA[Makes working with JSON in .NET feel effortless. It’s fast, flexible, and packed with helpful features—like easy serialization, handy querying tools, and even XML support.]]></description>
                    <link>https://christian-schou.com/nugets/newtonsoft-json/</link>
                    <guid isPermaLink="false">69206c3d214bae0001e009c2</guid>


                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Fri, 21 Nov 2025 14:42:51 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2025/11/nugets-json-dotnet.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2025/11/nugets-json-dotnet.webp" alt="Newtonsoft.Json"/> <p>here are the texts</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[RestSharp]]></title>
                    <description><![CDATA[RestSharp simplifies HTTP calls with easy XML/JSON handling, a fully async API, and one-line support for queries, forms, and file uploads.]]></description>
                    <link>https://christian-schou.com/nugets/restsharp/</link>
                    <guid isPermaLink="false">69201834214bae0001e00974</guid>


                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Fri, 21 Nov 2025 08:44:04 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2025/11/nugets-restsharp.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2025/11/nugets-restsharp.webp" alt="RestSharp"/> <h2 id="welcome">Welcome</h2><p>Some text to the page</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Using Action and Func in C#]]></title>
                    <description><![CDATA[If you are working with C# on a daily basis, I am sure that you have ran into delegates, Action, and Func types. If you can understand the difference between these types and when you should use them, your code will end up being very flexible and maintainable at the]]></description>
                    <link>https://christian-schou.com/docs/advanced-csharp-features/using-action-and-func/</link>
                    <guid isPermaLink="false">691c5b98214bae0001e00912</guid>

                        <category><![CDATA[Advanced C# Features]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Tue, 18 Nov 2025 12:43:07 +0100</pubDate>


                    <content:encoded><![CDATA[<p>If you are working with C# on a daily basis, I am sure that you have ran into delegates, <code>Action</code>, and <code>Func</code> types. If you can understand the difference between these types and when you should use them, your code will end up being very flexible and maintainable at the same time.</p><h2 id="what-are-delegates">What Are Delegates?</h2><p>I will keep it ultra short. A delegate is a super type-safe function pointer in C#. They will allow you to pass a method as a parameter, store them in a variable, and invoke them later when you need it.</p><p>You can think of delegates as a contract that defines how your method should look like. interested in delegates? check out my Events and Delegates section in the <a href="https://christian-schou.com/collections/advanced-csharp-features/" rel="noreferrer">Advanced C# Features</a> collection.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://christian-schou.com/collections/advanced-csharp-features/understanding-delegates/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Understanding Delegates in C#</div><div class="kg-bookmark-description">In this section you will learn what delegates are all about, why they are so super useful, and how you can use them in an effective way in your own applications.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/cropped-cs-logo-color-retina-10.png" alt=""><span class="kg-bookmark-author">Tech with Christian</span><span class="kg-bookmark-publisher">Christian Schou Køster</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/twc-hero-banner-1-1.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><h2 id="building-a-data-processor">Building A Data Processor</h2><p>Alright, let's build something real. A scenario for a real world project could be a data processor. Data is the new gold, so why not make something that could process the data for us?</p><p>I will now show you a data processor that is capable of doing various mathematical operations- and validations on data. We will take off with some custom delegates, and then utilize Action, and Func to make the code simple.</p><p>Are you ready? Here are the delegates - one for validation, and one for calculation on the data.</p><pre><code class="language-csharp">namespace TWC.DelegateExamples;

public delegate bool ValidationDelegate(int value);
public delegate int CalculationDelegate(int a, int b);
</code></pre><p>That is pretty straight forward. Let's now implement the actual data processor.</p><pre><code class="language-csharp">namespace TWC.DelegateExamples;

public class DataProcessor
{
    // Using the first custom delegate
    public void ProcessWithValidation(int value, ValidationDelegate validator)
    {
        if (validator(value))
        {
            Console.WriteLine($"✓ Value {value} passed validation");
        }
        else
        {
            Console.WriteLine($"✗ Value {value} failed validation");
        }
    }

    // Using Action (for operations that don't return a value)
    public void ExecuteOperation(int a, int b, Action&lt;int, int&gt; operation)
    {
        Console.WriteLine($"Executing operation on {a} and {b}...");
        operation(a, b);
    }

    // Using Func (for operations that return a value)
    public void Calculate(int a, int b, Func&lt;int, int, int&gt; calculation)
    {
        var result = calculation(a, b);
        Console.WriteLine($"Calculation result: {result}");
    }

    // Func with different return type
    public void AnalyzeResult(int a, int b, Func&lt;int, int, string&gt; analyzer)
    {
        var analysis = analyzer(a, b);
        Console.WriteLine($"Analysis: {analysis}");
    }
}</code></pre><p>I know it is really simple, but it is only to show you the difference and maybe wake a light inside you for how awesome this is?! 😎</p><h2 id="using-the-data-processor">Using the Data Processor</h2><p>Okay now that we have the data processor in place with some simple logic, let's bring it to life.</p><pre><code class="language-csharp">public class Program
{
    public static void Main()
    {
        var processor = new DataProcessor();

        // 1. Custom Delegate Example
        ValidationDelegate isPositive = (value) =&gt; value &gt; 0;
        processor.ProcessWithValidation(42, isPositive);
        processor.ProcessWithValidation(-5, isPositive);

        // You can also pass the lambda directly
        processor.ProcessWithValidation(100, (value) =&gt; value % 2 == 0);

        Console.WriteLine();

        // 2. Action Example (no return value)
        processor.ExecuteOperation(10, 5, (a, b) =&gt; 
        {
            var sum = a + b;
            Console.WriteLine($"  Sum: {sum}");
        });

        processor.ExecuteOperation(10, 5, (a, b) =&gt; 
        {
            var product = a * b;
            Console.WriteLine($"  Product: {product}");
        });

        Console.WriteLine();

        // 3. Func Example (returns a value)
        processor.Calculate(15, 3, (a, b) =&gt; a / b);
        processor.Calculate(8, 7, (a, b) =&gt; a * b);

        Console.WriteLine();

        // 4. Func with different return type
        processor.AnalyzeResult(20, 10, (a, b) =&gt; 
        {
            var ratio = (double)a / b;
            return ratio &gt; 1.5 ? "Significant difference" : "Similar values";
        });
    }
}</code></pre><p>If you a new to custom <code>delegates</code>, <code>Action</code>, or <code>Func</code>. The code above might seem a bit confusing, I totally get that. So... please let me explain the difference.</p><h2 id="key-differences-explained">Key Differences Explained</h2><h3 id="custom-delegates">Custom Delegates</h3><ul><li><strong>When to use?</strong> When you want to define a reusable <strong>way a method should look</strong> for your domain.</li><li><strong>Pros?</strong> Easier to understand because the name tells you what it’s meant to do.</li><li><strong>Cons?</strong> Requires explicit declaration.</li></ul><h3 id="actiont1-t2">Action&lt;T1, T2, ...&gt;</h3><ul><li><strong>When to use?</strong> When you need to pass a method that performs an operation but doesn't return a value.</li><li>A <strong>signature</strong> can take 0-16 parameters, returns <code>void</code>.</li><li>An <strong>example</strong> could be <code>Action&lt;int, int&gt;</code> that takes two integers and returns nothing.</li></ul><p>In C#, <strong>a method’s signature</strong> is the <em>shape</em> of the method. The word signature might seem quite technical for some, but using it daily will start making it the default go-to <strong>😅</strong> Signature is the specific combination of the following in a method.</p><ol><li><strong>Its name</strong></li><li><strong>Its parameter list</strong> (<em>types, number, </em>and<em> order</em>)</li><li><strong>Its return type</strong> (For <code>delegates</code> and <code>interfaces</code>, the <strong>return type</strong> is considered part of the signature. In class methods it doesn’t affect overload resolution, though it still contributes to how we think about the method’s overall structure. Makes sense? 😄)</li></ol><h3 id="funct1-t2-tresult">Func&lt;T1, T2, ..., TResult&gt;</h3><ul><li><strong>When to use?</strong> When you need to pass a method that calculates or returns a value, like in my example above.</li><li>Again <strong>signature</strong> can take 0-16 parameters. Remember that <strong>the last type parameter is always the return type!</strong></li><li>An <strong>example</strong> could be <code>Func&lt;int, int, string&gt;</code> that takes two integers and returns a string.</li></ul><h2 id="when-to-use-what">When To Use What?</h2><p>I get it... it might seem overwhelming or confusing. To fix that, I have created a diagram that might help you.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://christian-schou.com/content/images/2025/11/custom-delegate-action-or-func.webp" class="kg-image" alt="when to use custom delegate, action, or func in C#" loading="lazy" width="2000" height="2019" srcset="https://christian-schou.com/content/images/size/w600/2025/11/custom-delegate-action-or-func.webp 600w, https://christian-schou.com/content/images/size/w1000/2025/11/custom-delegate-action-or-func.webp 1000w, https://christian-schou.com/content/images/size/w1600/2025/11/custom-delegate-action-or-func.webp 1600w, https://christian-schou.com/content/images/size/w2400/2025/11/custom-delegate-action-or-func.webp 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Diagram to select between </span><b><strong style="white-space: pre-wrap;">custom delegate</strong></b><span style="white-space: pre-wrap;">, </span><code spellcheck="false" style="white-space: pre-wrap;"><span>Action</span></code><span style="white-space: pre-wrap;">, or </span><code spellcheck="false" style="white-space: pre-wrap;"><span>Func</span></code></figcaption></figure><h2 id="real-world-use-cases">Real-World Use Cases</h2><p>To make things more simple or easier to undertand, I have broken them down into some real-world use cases.</p><p><strong>Custom Delegates</strong> should be used for Event handlers, and callback patterns with specific domain meaning.</p><pre><code class="language-csharp">public delegate void OrderProcessedDelegate(Order order, DateTime timestamp);</code></pre><p><strong>Action</strong> should also be used for Event handlers, but also async callbacks, and LINQ operations like <code>ForEach()</code>.</p><pre><code class="language-csharp">items.ForEach(item =&gt; Console.WriteLine(item));</code></pre><p><strong>Func</strong> should be used for LINQ queries, factory patterns, and transformation pipelines.</p><pre><code class="language-csharp">var doubled = numbers.Select(n =&gt; n * 2);
var filtered = items.Where(item =&gt; item.IsActive);</code></pre><h2 id="my-best-practices">My Best Practices</h2><ul><li>I prefer <code>Action</code> and <code>Func</code> for most cases. Why? They are baked-in and universally understood.</li><li>I use custom delegates when the name adds clarity to my domain (like <code>ValidationDelegate</code>).</li><li>I Keep lambdas simple. If they're complex, extract them to named methods. Will make your life so much easier 🙌</li><li>I always consider async variants. This could be <code>Func&lt;Task&gt;</code> and <code>Func&lt;Task&lt;T&gt;&gt;</code> for async operations.</li></ul><h2 id="summary">Summary</h2><p>Custom delegates, <code>Action</code>, and <code>Func</code> all overlap each other in terms of purpose. If you want to write idiomatic C# code, you need to understand their differences. I always use custom delegates when a clear, and domain-drive name will provide me value. Finally I use <code>Action</code> and <code>Func</code> when I would like to write some code that provide a simple, and convenient callback.</p><p>What are you using and why? Please let me know about that and any other related questions in the comments. Thank you for reading this far! ✨</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Event Subscription and Management]]></title>
                    <description><![CDATA[Final section of events and delegates for now. We have previously seen how delegates and events work. Events are nothing without one or more subscribers.

In this final post I will show you how to subscribe to events, manage subscriptions, and how you can avoid common pitfalls that in most]]></description>
                    <link>https://christian-schou.com/docs/advanced-csharp-features/event-subscription-and-management/</link>
                    <guid isPermaLink="false">691c201c214bae0001e008db</guid>

                        <category><![CDATA[Advanced C# Features]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Tue, 18 Nov 2025 08:32:52 +0100</pubDate>


                    <content:encoded><![CDATA[<p>Final section of events and delegates for now. We have previously seen how delegates and events work. Events are nothing without one or more subscribers.</p><p>In this final post I will show you how to subscribe to events, manage subscriptions, and how you can avoid common pitfalls that in most cases lead to memory leaks. By the end you will know how the publisher -subscriber pattern works and how you can implement that in a .NET application.</p><h2 id="the-publisher-subscriber-pattern">The Publisher-Subscriber Pattern</h2><p>I won't deep dive into this pattern theoretically. I will keep it short and then focus on how to implement it instead. The publisher-subscriber (often referred to as pub-sub) is a messaging pattern where you <strong>three</strong> primary thing.</p><ul><li>The <strong>publisher</strong> is the one responsible for raising events. This could be a worker or an order processor, etc...</li><li>A <strong>subscriber</strong> that will listen to and act/handle events that has been raised by the publisher.</li><li>And finally we have the <strong>event</strong>, which can also be seen as an internal notification mechanism between the publisher and the subscriber.</li></ul><p>One thing I really like about this pattern is that it promotes <strong>loose coupling</strong>. Why? Publishers won't know a thing about who their subscribers are, and the subscribers don't know anything about the implementation of the publishers.</p><p>Alright - let's get to the fun part and write some code. I will start with the basics and advance along the way all to the bottom of this post.</p><h2 id="basic-event-subscription">Basic Event Subscription</h2><p>Let's start out with a basic example. This is a task manager with a task logger as the subscriber. </p><pre><code class="language-csharp">public class TaskManager
{
    public event EventHandler&lt;TaskEventArgs&gt;? TaskCompleted;
    
    public void CompleteTask(string taskName)
    {
        Console.WriteLine($"Completing task: {taskName}");
        TaskCompleted?.Invoke(this, new TaskEventArgs(taskName));
    }
}

public class TaskEventArgs : EventArgs
{
    public string TaskName { get; }
    public TaskEventArgs(string taskName) =&gt; TaskName = taskName;
}

// Subscriber
public class TaskLogger
{
    public void Subscribe(TaskManager manager)
    {
        // Subscribe using += operator
        manager.TaskCompleted += OnTaskCompleted;
    }
    
    private void OnTaskCompleted(object? sender, TaskEventArgs e)
    {
        Console.WriteLine($"  [Logger] Task logged: {e.TaskName}");
    }
}

// Usage
var manager = new TaskManager();
var logger = new TaskLogger();

logger.Subscribe(manager);
manager.CompleteTask("Deploy Application");

/* Output:
Completing task: Deploy Application
  [Logger] Task logged: Deploy Application
*/</code></pre><p>There is not much to say about it. It has a manager responsible for managing the tasks, and then we have the subscriber that will subscribe to the manager and handle events it will publish.</p><h2 id="multiple-subscribers">Multiple Subscribers</h2><p>A cool feature of events is that multiple subscribers can listen for the same event. This gives us the option to do multiple different things for a single event, like shown below.</p><pre><code class="language-csharp">public class EmailNotifier
{
    public void Subscribe(TaskManager manager)
    {
        manager.TaskCompleted += (sender, e) =&gt;
        {
            Console.WriteLine($"  [Email] Notification sent for: {e.TaskName}");
        };
    }
}

public class MetricsCollector
{
    public void Subscribe(TaskManager manager)
    {
        manager.TaskCompleted += (sender, e) =&gt;
        {
            Console.WriteLine($"  [Metrics] Recorded completion of: {e.TaskName}");
        };
    }
}

// Usage
var manager = new TaskManager();
var logger = new TaskLogger();
var emailer = new EmailNotifier();
var metrics = new MetricsCollector();

logger.Subscribe(manager);
emailer.Subscribe(manager);
metrics.Subscribe(manager);

manager.CompleteTask("Database Backup");

/* Output:
Completing task: Database Backup
  [Logger] Task logged: Database Backup
  [Email] Notification sent for: Database Backup
  [Metrics] Recorded completion of: Database Backup
*/</code></pre><p>A simple example of multiple subscribers for a single event. As you can see we could be sending emails to the admin team, and update maybe Prometheus with some metrics once this events was raised.</p><h2 id="why-it-is-important-to-unsubscribe">Why It Is Important To Unsubscribe</h2><div class="kg-card kg-cta-card kg-cta-bg-red kg-cta-minimal    " data-layout="minimal">
            
                <div class="kg-cta-sponsor-label-wrapper">
                    <div class="kg-cta-sponsor-label">
                        <span style="white-space: pre-wrap;">Memory Leak Alert</span>
                    </div>
                </div>
            
            <div class="kg-cta-content">
                
                
                    <div class="kg-cta-content-inner">
                    
                        <div class="kg-cta-text">
                            <p dir="ltr"><span style="white-space: pre-wrap;">When you subscribe to an event, the publisher will hold a reference to the subscriber. If you do not unsubscribe, the subscriber can never be garbage collected, even if you are done using it. This will lead to a memory leak!</span></p>
                        </div>
                    
                    
                    </div>
                
            </div>
        </div><p>Let's take a look at how we successfully can unsubscribe from the event again.</p><pre><code class="language-csharp">public class TaskMonitor
{
    private TaskManager _manager;
    
    public TaskMonitor(TaskManager manager)
    {
        _manager = manager;
    }
    
    public void Subscribe()
    {
        Console.WriteLine("  → Subscribing to events");
        _manager.TaskCompleted += OnTaskCompleted;
    }
    
    public void Unsubscribe()
    {
        Console.WriteLine("  → Unsubscribing from events");
        _manager.TaskCompleted -= OnTaskCompleted; // Important!
    }
    
    private void OnTaskCompleted(object? sender, TaskEventArgs e)
    {
        Console.WriteLine($"  [Monitor] Tracked: {e.TaskName}");
    }
    
    // Finalizer to clean up
    ~TaskMonitor()
    {
        Unsubscribe();
    }
}

// Usage demonstrating proper cleanup
var manager = new TaskManager();
var monitor = new TaskMonitor(manager);

monitor.Subscribe();
manager.CompleteTask("First Task");

monitor.Unsubscribe();
manager.CompleteTask("Second Task"); // Monitor won't receive this

/* Output:
  → Subscribing to events
Completing task: First Task
  [Monitor] Tracked: First Task
  → Unsubscribing from events
Completing task: Second Task
*/</code></pre><h2 id="how-to-store-event-handlers-for-correct-unsubscription">How To Store Event Handlers for Correct Unsubscription</h2><p>If or when you are using a lambda expression or an anonymous method, you would need to store a reference somewhere to unsubscribe from the event correctly. Let's see how we could accomplish that.</p><pre><code class="language-csharp">public class NotificationService
{
    private TaskManager _manager;
    private EventHandler&lt;TaskEventArgs&gt; _taskHandler;
    
    public NotificationService(TaskManager manager)
    {
        _manager = manager;
        // Store the handler reference
        _taskHandler = (sender, e) =&gt;
        {
            Console.WriteLine($"  [Notification] {e.TaskName} completed!");
        };
    }
    
    public void Subscribe()
    {
        Console.WriteLine("  → Subscribing");
        _manager.TaskCompleted += _taskHandler;
    }
    
    public void Unsubscribe()
    {
        Console.WriteLine("  → Unsubscribing");
        _manager.TaskCompleted -= _taskHandler; // Works because we stored it
    }
}

// Usage
var manager = new TaskManager();
var notifier = new NotificationService(manager);

notifier.Subscribe();
manager.CompleteTask("Build Project");

notifier.Unsubscribe();
manager.CompleteTask("Run Tests"); // Notifier won't receive this</code></pre><h3 id="the-problem-with-lambda-expressions">The Problem With Lambda Expressions</h3><p>A word of warning. An inline lambda expression cannot be unsubscribed. Just saying it 😄 (<em>an advice from a friend of mine</em>)</p><pre><code class="language-csharp">var manager = new TaskManager();

// ❌ PROBLEM - Cannot unsubscribe
manager.TaskCompleted += (sender, e) =&gt;
{
    Console.WriteLine($"Handled: {e.TaskName}");
};

// This does NOT work - creates a new lambda, doesn't remove the old one
manager.TaskCompleted -= (sender, e) =&gt;
{
    Console.WriteLine($"Handled: {e.TaskName}");
};

// ✅ SOLUTION - Store the handler
EventHandler&lt;TaskEventArgs&gt; handler = (sender, e) =&gt;
{
    Console.WriteLine($"Handled: {e.TaskName}");
};

manager.TaskCompleted += handler;
// Later...
manager.TaskCompleted -= handler; // This works! 😎</code></pre><p>Okay, now that we have seen how to unsubscribe from events of different kinds, I think it is time to bring it all together and build something that makes sense.</p><h2 id="building-a-stock-trading-system-with-events">Building A Stock Trading System With Events</h2><p>Below is the code for a system that implement event management for a stock trading system. Who doesn't like money?! 💰 Let's split it into multiple sections.</p><h3 id="eventargs">EventArgs</h3><pre><code class="language-csharp">public class StockPriceChangedEventArgs : EventArgs
{
    public string Symbol { get; }
    public decimal OldPrice { get; }
    public decimal NewPrice { get; }
    public decimal Change =&gt; NewPrice - OldPrice;
    public decimal ChangePercent =&gt; (Change / OldPrice) * 100;
    
    public StockPriceChangedEventArgs(string symbol, decimal oldPrice, decimal newPrice)
    {
        Symbol = symbol;
        OldPrice = oldPrice;
        NewPrice = newPrice;
    }
}</code></pre><h3 id="publisher">Publisher</h3><pre><code class="language-csharp">public class StockMarket
{
    public event EventHandler&lt;StockPriceChangedEventArgs&gt;? PriceChanged;
    
    private Dictionary&lt;string, decimal&gt; _prices = new();
    
    public void UpdatePrice(string symbol, decimal newPrice)
    {
        decimal oldPrice = _prices.GetValueOrDefault(symbol, newPrice);
        _prices[symbol] = newPrice;
        
        if (oldPrice != newPrice)
        {
            OnPriceChanged(symbol, oldPrice, newPrice);
        }
    }
    
    protected virtual void OnPriceChanged(string symbol, decimal oldPrice, decimal newPrice)
    {
        var args = new StockPriceChangedEventArgs(symbol, oldPrice, newPrice);
        PriceChanged?.Invoke(this, args);
    }
}</code></pre><h3 id="subscribers">Subscribers</h3><p>The system will have multiple subscribers, each having different behavior.</p><h4 id="subscriber-1alert-service">Subscriber 1 - Alert Service</h4><pre><code class="language-csharp">public class AlertService
{
    private StockMarket _market;
    private EventHandler&lt;StockPriceChangedEventArgs&gt; _priceChangedHandler;
    
    public AlertService(StockMarket market)
    {
        _market = market;
        _priceChangedHandler = OnPriceChanged;
    }
    
    public void Subscribe()
    {
        Console.WriteLine("  [Alert Service] Subscribed");
        _market.PriceChanged += _priceChangedHandler;
    }
    
    public void Unsubscribe()
    {
        Console.WriteLine("  [Alert Service] Unsubscribed");
        _market.PriceChanged -= _priceChangedHandler;
    }
    
    private void OnPriceChanged(object? sender, StockPriceChangedEventArgs e)
    {
        if (Math.Abs(e.ChangePercent) &gt; 5)
        {
            Console.WriteLine($"  🚨 ALERT: {e.Symbol} moved {e.ChangePercent:F2}% " +
                            $"(${e.OldPrice} → ${e.NewPrice})");
        }
    }
    
    ~AlertService()
    {
        Unsubscribe();
    }
}</code></pre><h4 id="subscriber-2the-trading-bot">Subscriber 2 - The Trading Bot</h4><pre><code class="language-csharp">public class TradingBot
{
    private StockMarket _market;
    private EventHandler&lt;StockPriceChangedEventArgs&gt; _priceChangedHandler;
    
    public TradingBot(StockMarket market)
    {
        _market = market;
        _priceChangedHandler = OnPriceChanged;
    }
    
    public void Subscribe()
    {
        Console.WriteLine("  [Trading Bot] Subscribed");
        _market.PriceChanged += _priceChangedHandler;
    }
    
    public void Unsubscribe()
    {
        Console.WriteLine("  [Trading Bot] Unsubscribed");
        _market.PriceChanged -= _priceChangedHandler;
    }
    
    private void OnPriceChanged(object? sender, StockPriceChangedEventArgs e)
    {
        if (e.Change &gt; 0)
        {
            Console.WriteLine($"  🤖 BOT: {e.Symbol} trending up! Consider buying.");
        }
        else if (e.Change &lt; 0)
        {
            Console.WriteLine($"  🤖 BOT: {e.Symbol} trending down! Consider selling.");
        }
    }
    
    ~TradingBot()
    {
        Unsubscribe();
    }
}</code></pre><h4 id="subscriber-3market-logger">Subscriber 3 - Market Logger</h4><pre><code class="language-csharp">public class MarketLogger
{
    private StockMarket _market;
    private EventHandler&lt;StockPriceChangedEventArgs&gt; _priceChangedHandler;
    
    public MarketLogger(StockMarket market)
    {
        _market = market;
        _priceChangedHandler = OnPriceChanged;
    }
    
    public void Subscribe()
    {
        Console.WriteLine("  [Market Logger] Subscribed");
        _market.PriceChanged += _priceChangedHandler;
    }
    
    public void Unsubscribe()
    {
        Console.WriteLine("  [Market Logger] Unsubscribed");
        _market.PriceChanged -= _priceChangedHandler;
    }
    
    private void OnPriceChanged(object? sender, StockPriceChangedEventArgs e)
    {
        Console.WriteLine($"  📝 LOG: {DateTime.Now:HH:mm:ss} - {e.Symbol}: " +
                        $"${e.OldPrice:F2} → ${e.NewPrice:F2} " +
                        $"({e.ChangePercent:+0.00;-0.00}%)");
    }
    
    ~MarketLogger()
    {
        Unsubscribe();
    }
}</code></pre><h3 id="wirering-it-all-up">Wirering it all up</h3><pre><code class="language-csharp">public class StockTradingDemo
{
    public static void Run()
    {
        var market = new StockMarket();
        var alerts = new AlertService(market);
        var bot = new TradingBot(market);
        var logger = new MarketLogger(market);
        
        // Subscribe all services
        Console.WriteLine("=== Subscribing Services ===");
        alerts.Subscribe();
        bot.Subscribe();
        logger.Subscribe();
        
        Console.WriteLine("\n=== Price Updates ===");
        market.UpdatePrice("AAPL", 150.00m);
        market.UpdatePrice("AAPL", 155.00m); // +3.33%
        market.UpdatePrice("AAPL", 165.00m); // +6.45% - triggers alert!
        
        Console.WriteLine("\n=== Unsubscribing Alert Service ===");
        alerts.Unsubscribe();
        
        Console.WriteLine("\n=== More Price Updates ===");
        market.UpdatePrice("GOOGL", 2800.00m);
        market.UpdatePrice("GOOGL", 2600.00m); // -7.14% - alert service won't see this!
        
        Console.WriteLine("\n=== Cleanup ===");
        bot.Unsubscribe();
        logger.Unsubscribe();
    }
}</code></pre><h3 id="the-result">The Result</h3><pre><code class="language-text">=== Subscribing Services ===
  [Alert Service] Subscribed
  [Trading Bot] Subscribed
  [Market Logger] Subscribed

=== Price Updates ===
  🤖 BOT: AAPL trending up! Consider buying.
  📝 LOG: 14:30:15 - AAPL: $150.00 → $155.00 (+3.33%)
  🤖 BOT: AAPL trending up! Consider buying.
  📝 LOG: 14:30:15 - AAPL: $155.00 → $165.00 (+6.45%)
  🚨 ALERT: AAPL moved 6.45% ($155.00 → $165.00)

=== Unsubscribing Alert Service ===
  [Alert Service] Unsubscribed

=== More Price Updates ===
  📝 LOG: 14:30:15 - GOOGL: $2800.00 → $2600.00 (-7.14%)
  🤖 BOT: GOOGL trending down! Consider selling.
  📝 LOG: 14:30:15 - GOOGL: $2600.00 → $2650.00 (+1.92%)

=== Cleanup ===
  [Trading Bot] Unsubscribed
  [Market Logger] Unsubscribed</code></pre><h2 id="my-top-advices-for-event-subscriptions">My Top Advices for Event Subscriptions</h2><p>Here are some of my top advices that I really recommend you to follow when you are working with event subscriptions.</p><h3 id="always-store-event-handlers-when-using-lambdas">Always Store Event Handlers When Using Lambdas</h3><pre><code class="language-csharp">private EventHandler&lt;EventArgs&gt; _handler;

public void Subscribe()
{
    _handler = (s, e) =&gt; HandleEvent();
    publisher.Event += _handler;
}

public void Unsubscribe()
{
    publisher.Event -= _handler;
}</code></pre><h3 id="implement-idisposable-for-cleanup">Implement IDisposable for Cleanup</h3><pre><code class="language-csharp">public class EventSubscriber : IDisposable
{
    private StockMarket _market;
    private EventHandler&lt;StockPriceChangedEventArgs&gt; _handler;
    
    public EventSubscriber(StockMarket market)
    {
        _market = market;
        _handler = OnPriceChanged;
        _market.PriceChanged += _handler;
    }
    
    private void OnPriceChanged(object? sender, StockPriceChangedEventArgs e)
    {
        // Handle event
    }
    
    public void Dispose()
    {
        _market.PriceChanged -= _handler;
    }
}

// Usage with using statement
using (var subscriber = new EventSubscriber(market))
{
    // Subscriber is active here
} // Automatically unsubscribed when disposed</code></pre><h3 id="use-weak-event-pattern-for-long-lived-publishers">Use Weak Event Pattern for Long-Lived Publishers</h3><p>If you have publishers in your application that will live longer than the subscribers, I would recommend you to use a weak reference to prevent a memory leak. This is advanced, but it is important to know about. An example would be an application-level singleton - that would probably live longer than your subscriber.</p><h3 id="be-careful-with-lambda-expressions">Be Careful with Lambda Expressions</h3><pre><code class="language-csharp">// ❌ You can't unsubscribe from this
publisher.Event += (s, e) =&gt; Console.WriteLine("Event!");

// ✅ A named method
publisher.Event += OnEvent;
void OnEvent(object? s, EventArgs e) =&gt; Console.WriteLine("Event!");

// ✅ A stored lambda
var handler = (object? s, EventArgs e) =&gt; Console.WriteLine("Event!");
publisher.Event += handler;</code></pre><h2 id="common-pitfalls-and-solutions">Common Pitfalls and Solutions</h2><div class="kg-card kg-toggle-card" data-kg-toggle-state="close">
            <div class="kg-toggle-heading">
                <h4 class="kg-toggle-heading-text"><span style="white-space: pre-wrap;">Forgetting to Unsubscribe</span></h4>
                <button class="kg-toggle-card-icon" aria-label="Expand toggle to read content">
                    <svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                        <path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"></path>
                    </svg>
                </button>
            </div>
            <div class="kg-toggle-content"><p dir="ltr"><span style="white-space: pre-wrap;">The</span><b><strong style="white-space: pre-wrap;"> problem</strong></b><span style="white-space: pre-wrap;"> is that your subscribers never get garbage collected.</span></p><p dir="ltr"><span style="white-space: pre-wrap;">And the</span><b><strong style="white-space: pre-wrap;"> solution</strong></b><span style="white-space: pre-wrap;"> is to always unsubscribe in finalizers or </span><code spellcheck="false" style="white-space: pre-wrap;"><span>Dispose()</span></code><span style="white-space: pre-wrap;"> methods.</span></p></div>
        </div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close">
            <div class="kg-toggle-heading">
                <h4 class="kg-toggle-heading-text"><span style="white-space: pre-wrap;">Subscribing Multiple Times</span></h4>
                <button class="kg-toggle-card-icon" aria-label="Expand toggle to read content">
                    <svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                        <path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"></path>
                    </svg>
                </button>
            </div>
            <div class="kg-toggle-content"><p dir="ltr"><span style="white-space: pre-wrap;">The </span><b><strong style="white-space: pre-wrap;">problem </strong></b><span style="white-space: pre-wrap;">is that your handler gets called multiple times per event.</span></p><p dir="ltr"><span style="white-space: pre-wrap;">The s</span><b><strong style="white-space: pre-wrap;">olution</strong></b><span style="white-space: pre-wrap;"> is to make sure you unsubscribe before subscribing, or check if already subscribed.</span></p><p dir="ltr"><span style="white-space: pre-wrap;">publisher.Event -= OnEvent; // Remove if exists</span><br><span style="white-space: pre-wrap;">publisher.Event += OnEvent; // Add fresh subscription</span></p></div>
        </div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close">
            <div class="kg-toggle-heading">
                <h4 class="kg-toggle-heading-text"><span style="white-space: pre-wrap;">Exception in Event Handler</span></h4>
                <button class="kg-toggle-card-icon" aria-label="Expand toggle to read content">
                    <svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                        <path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"></path>
                    </svg>
                </button>
            </div>
            <div class="kg-toggle-content"><p dir="ltr"><span style="white-space: pre-wrap;">Your </span><b><strong style="white-space: pre-wrap;">problem</strong></b><span style="white-space: pre-wrap;"> is that if one subscriber throws an exception, subsequent subscribers don't get notified.</span></p><p dir="ltr"><span style="white-space: pre-wrap;">A </span><b><strong style="white-space: pre-wrap;">solution</strong></b><span style="white-space: pre-wrap;"> is to make sure publishers should catch exceptions when invoking events (</span><i><em class="italic" style="white-space: pre-wrap;">advanced technique</em></i><span style="white-space: pre-wrap;">).</span></p></div>
        </div><h2 id="key-takeaways">Key Takeaways</h2><p>This time listed as dots and not a plain text. Here are 7 things I would recommend you to write down by hand to memorize it both in your head and hand.</p><ol><li>Remember to always <strong>unsubscribe from events</strong> when you're done with them.</li><li><strong>Always store lambda expression references</strong> if you need to unsubscribe later.</li><li>Make sure to use <strong>finalizers</strong> or <code>IDisposable</code> to make sure you clean up properly.</li><li>A single event can have multiple subscribers.</li><li>Using events will promote loose coupling in your application between the publishers and subscribers.</li><li>Making inline lambda expressions cannot be unsubscribed from. Why? They will create a new instance each time they are invoked.</li><li>Implementing the pub-sub pattern correctly, will make sure that your application is maintainable and can scale in the future.</li></ol><h2 id="you-made-it">You Made It!</h2><p>Congratulations! You made it to the end of my events and delegates section. You should now have a good understanding of the following.</p><ol><li><strong>Delegates</strong> are type-safe function pointers. We can use them to enable callbacks.</li><li><strong>Events</strong> are encapsulated delegates that makes the publisher-subscriber pattern possible.</li><li><strong>Subscription Management</strong> is a technique to make sure that you can subscribe, unsubscribe, and avoid memory leaks in your application.</li></ol><p>What you just went trough is fundamental to C# and .NET. It runs everything from UI frameworks like WPF or WinForms to huge complex backends with multiple services, and reactive programming.</p><h2 id="additional-resources">Additional Resources</h2><p>Some good resources you should take your time to read, if you got it.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Events - C#</div><div class="kg-bookmark-description">Learn about events. Events enable a class or object to notify other classes or objects when something of interest occurs.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-25.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">BillWagner</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/logo_csharp-5.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">.NET garbage collection - .NET</div><div class="kg-bookmark-description">Learn about garbage collection in .NET. The .NET garbage collector manages the allocation and release of memory for your application.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-26.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">gewarren</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/dot-net-cross-platform-1.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Implement a Dispose method - .NET</div><div class="kg-bookmark-description">In this article, learn to implement the Dispose method, which releases unmanaged resources used by your code in .NET.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-27.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">gewarren</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/dot-net-cross-platform-2.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Understanding Events in C#]]></title>
                    <description><![CDATA[I assume you have basic knowledge of delegates at this point. If not, please go back and read my first post in this section about events and delegates - Understanding Delegates in C#.

Using events we as the developers can provide a standardized way to implement the observer pattern in]]></description>
                    <link>https://christian-schou.com/docs/advanced-csharp-features/understanding-events/</link>
                    <guid isPermaLink="false">6917fe92214bae0001e00822</guid>

                        <category><![CDATA[Advanced C# Features]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Tue, 18 Nov 2025 07:36:38 +0100</pubDate>


                    <content:encoded><![CDATA[<p>I assume you have basic knowledge of delegates at this point. If not, please go back and read my first post in this section about events and delegates - <a href="https://christian-schou.com/collections/advanced-csharp-features/understanding-delegates/" rel="noreferrer">Understanding Delegates in C#</a>.</p><p>Using events we as the developers can provide a standardized way to implement the observer pattern in our applications. The observer pattern is a way for objects to notify other objects when something interesting happens.</p><h2 id="what-are-events">What Are Events?</h2><p>An event is a special type of a <a href="https://christian-schou.com/collections/advanced-csharp-features/understanding-delegates/#multicast-delegatesnotify-multiple-recipients" rel="noreferrer">multicast delegate</a>, where only the class declaring it can invoke it. Ever heard of encapsulation? Here you have it! Using events will encapsulate the logic and that is the very difference from a regular delegate.</p><p>I often tell other to think of events as a publish-subscribe setup.</p><ul><li>The <strong>publisher</strong> will raise an event when something happens. This could be a domain event (<code>UserCreatedDomainEvent</code>).</li><li>The <strong>subscriber</strong> will listen for the events (the domain event in this example), and it will respond when the event arrives.</li><li>A subscriber is a standalone piece of code. They should not interact/affect others behavior</li><li>A subscriber should not trigger events itself. Only the event publisher should be allowed for raising it. e<em>.g. an entity/domain object</em>.</li></ul><h2 id="why-use-events-instead-of-plain-delegates">Why Use Events Instead of Plain Delegates?</h2><p>When using an event you are promised some safety, that is not the case with delegates. Let me show you 😎</p><pre><code class="language-csharp">// ❌ Problem with public delegates
public delegate void WorkDelegate(int hours);
public WorkDelegate WorkPerformed; // Anyone can invoke this!

// ✅ Solution with events
public event WorkDelegate WorkPerformed; // Only this class can invoke</code></pre><p>By using events, external code is only able to subscribe (<code>+=</code>) or unsubscribe <code>-=</code>. It won't be able to invoke the event or clear any subscriber of the event.</p><h2 id="creating-custom-events-with-custom-delegates">Creating Custom Events with Custom Delegates</h2><p>Ever heard of workers? I will now show you how to build a worker system that can track when work is done. The first thing you will have to do is defining a custom delegate, and event.</p><h3 id="define-a-custom-delegate">Define a custom delegate</h3><pre><code class="language-csharp">// 1. Define a custom delegate
public delegate void WorkPerformedDelegate(int hours, string workType);

public class Worker
{
    // 2. Declare an event using that delegate
    public event WorkPerformedDelegate? WorkPerformed;
    
    // 3. Method to perform work
    public void DoWork(int hours, string workType)
    {
        Console.WriteLine($"Working on {workType} for {hours} hours...");
        
        // 4. Raise the event
        OnWorkPerformed(hours, workType);
    }
    
    // 5. Protected method to raise the event
    // This pattern allows derived classes to customize event raising
    protected virtual void OnWorkPerformed(int hours, string workType)
    {
        // Invoke the event if there are subscribers
        WorkPerformed?.Invoke(hours, workType);
    }
}</code></pre><h3 id="using-the-custom-event">Using the custom event</h3><pre><code class="language-csharp">var worker = new Worker();

// Subscribe to the event
worker.WorkPerformed += (hours, workType) =&gt;
{
    Console.WriteLine($"  → Logged: {hours} hours of {workType}");
};

worker.WorkPerformed += (hours, workType) =&gt;
{
    Console.WriteLine($"  → Notified manager about {workType}");
};

// Trigger the work
worker.DoWork(8, "Development");

/* Output:
Working on Development for 8 hours...
  → Logged: 8 hours of Development
  → Notified manager about Development
*/</code></pre><h2 id="creating-custom-eventargs">Creating Custom EventArgs</h2><p>Instead of passing multiple parameters, it's a best practice to create a custom <code>EventArgs</code> class:</p><pre><code class="language-csharp">// Custom EventArgs class
public class WorkPerformedEventArgs : EventArgs
{
    public int Hours { get; }
    public string WorkType { get; }
    public DateTime Timestamp { get; }
    
    public WorkPerformedEventArgs(int hours, string workType)
    {
        Hours = hours;
        WorkType = workType;
        Timestamp = DateTime.Now;
    }
}</code></pre><p>Why would you do that? 🤔 It's simple..</p><ul><li>Having a custom class for event arguments, allows you to add new properties without breaking any of your existing code.</li><li>You can group related data in a single object.</li><li>It will follow standard .NET conventions.</li></ul><h2 id="using-the-built-in-generic-eventhandlert">Using The Built-in Generic EventHandler&lt;T&gt;</h2><p>In C# we got something named <code>EventHandler&lt;T&gt;</code> and it is generic. Using this you can get rid of the custom delegates. 🙌 Let me show you how we can refactor our code to use that generic instead.</p><h3 id="defining-the-eventhandler">Defining the EventHandler</h3><pre><code class="language-csharp">public class Worker
{
    // No custom delegate needed 👍
    // EventHandler&lt;T&gt; is defined as:
    // public delegate void EventHandler&lt;TEventArgs&gt;(object? sender, TEventArgs e)
    
    public event EventHandler&lt;WorkPerformedEventArgs&gt;? WorkPerformed;
    
    public void DoWork(int hours, string workType)
    {
        Console.WriteLine($"Starting: {workType}");
        
        for (int i = 1; i &lt;= hours; i++)
        {
            // Simulate work
            Console.WriteLine($"  Hour {i}...");
            
            // Raise event
            OnWorkPerformed(i, workType);
        }
        
        Console.WriteLine("Work completed!");
    }
    
    protected virtual void OnWorkPerformed(int hours, string workType)
    {
        var args = new WorkPerformedEventArgs(hours, workType);
        WorkPerformed?.Invoke(this, args);
    }
}</code></pre><p>Clean and easy to maintain! 😎</p><h3 id="using-the-generic-event">Using The Generic Event</h3><p>Now that we have defined the generic event, let's write some code that can actually use it.</p><pre><code class="language-csharp">var worker = new Worker();

worker.WorkPerformed += (sender, e) =&gt;
{
    Console.WriteLine($"    [Logger] {e.Hours}h on {e.WorkType} at {e.Timestamp:HH:mm:ss}");
};

worker.DoWork(3, "Code Review");

/* Output:
Starting: Code Review
  Hour 1...
    [Logger] 1h on Code Review at 14:30:15
  Hour 2...
    [Logger] 2h on Code Review at 14:30:15
  Hour 3...
    [Logger] 3h on Code Review at 14:30:15
Work completed!
*/</code></pre><p>The 1h, 2h, and 3h will arrive within the same second as we have no timer in the for loop earlier, but this was only meant for simulating some work. Let's see how we can use the built-in event handler to avoid any custom data.</p><h2 id="using-built-in-eventhandler">Using Built-in EventHandler</h2><p>When you have an event that do not need any custom data, there is no need for the generic. Instead we can swop it, and use the non-generic <code>EventHandler</code>.</p><pre><code class="language-csharp">public class Worker
{
    public event EventHandler&lt;WorkPerformedEventArgs&gt;? WorkPerformed;
    public event EventHandler? WorkCompleted; // No custom data needed 👍
    
    public void DoWork(int hours, string workType)
    {
        for (int i = 1; i &lt;= hours; i++)
        {
            OnWorkPerformed(i, workType);
        }
        
        // Notify that all work is done
        OnWorkCompleted();
    }
    
    protected virtual void OnWorkPerformed(int hours, string workType)
    {
        var args = new WorkPerformedEventArgs(hours, workType);
        WorkPerformed?.Invoke(this, args);
    }
    
    protected virtual void OnWorkCompleted()
    {
        // EventArgs.Empty is a reusable empty instance
        WorkCompleted?.Invoke(this, EventArgs.Empty);
    }
}</code></pre><p>That is how easy it is to work with events in .NET - pretty cool right?! 🔥 Thanks to the awesome engineers behind C# and the .NET framework, we get a lot of features out-of-the box and we are not forced to re-invent the wheel. I love it! 😄</p><p>So with all those small examples, let's put them all in a dish and see what we can do.</p><h2 id="building-a-order-processing-system">Building a Order Processing System</h2><p>Let's take a real-world example where we will use what you just learned. This example is a simple order processing system that I just created.</p><pre><code class="language-csharp">// EventArgs for order processing
public class OrderProcessedEventArgs : EventArgs
{
    public string OrderId { get; }
    public decimal Amount { get; }
    public string Status { get; }
    
    public OrderProcessedEventArgs(string orderId, decimal amount, string status)
    {
        OrderId = orderId;
        Amount = amount;
        Status = status;
    }
}

public class OrderProcessor
{
    // Custom delegate with custom event
    public delegate void OrderValidationDelegate(string orderId);
    public event OrderValidationDelegate? OrderValidating;
    
    // Generic EventHandler&lt;T&gt;
    public event EventHandler&lt;OrderProcessedEventArgs&gt;? OrderProcessed;
    
    // Standard EventHandler
    public event EventHandler? ProcessingCompleted;
    
    public void ProcessOrder(string orderId, decimal amount)
    {
        Console.WriteLine($"\n{'='.ToString().PadLeft(50, '=')}");
        Console.WriteLine($"Processing Order: {orderId}");
        Console.WriteLine($"{'='.ToString().PadLeft(50, '=')}");
        
        // Raise custom event
        OnOrderValidating(orderId);
        
        // Simulate processing
        Console.WriteLine("  → Processing payment...");
        
        // Raise generic event
        OnOrderProcessed(orderId, amount, "Completed");
        
        // Raise standard event
        OnProcessingCompleted();
    }
    
    protected virtual void OnOrderValidating(string orderId)
    {
        Console.WriteLine($"  → Validating order {orderId}");
        OrderValidating?.Invoke(orderId);
    }
    
    protected virtual void OnOrderProcessed(string orderId, decimal amount, string status)
    {
        var args = new OrderProcessedEventArgs(orderId, amount, status);
        OrderProcessed?.Invoke(this, args);
    }
    
    protected virtual void OnProcessingCompleted()
    {
        ProcessingCompleted?.Invoke(this, EventArgs.Empty);
    }
}

// Usage
var processor = new OrderProcessor();

// Subscribe to validation event (custom delegate)
processor.OrderValidating += (orderId) =&gt;
{
    Console.WriteLine($"    ✓ Validated: {orderId}");
};

// Subscribe to processed event (generic EventHandler&lt;T&gt;)
processor.OrderProcessed += (sender, e) =&gt;
{
    Console.WriteLine($"    ✓ Order {e.OrderId}: ${e.Amount} - {e.Status}");
};

// Subscribe to completion event (standard EventHandler)
processor.ProcessingCompleted += (sender, e) =&gt;
{
    Console.WriteLine("    ✓ All processing completed!");
    Console.WriteLine($"{'='.ToString().PadLeft(50, '=')}");
};

// Process orders
processor.ProcessOrder("ORD-001", 99.99m);
processor.ProcessOrder("ORD-002", 149.50m);</code></pre><p>Code is clean and easy for other engineers to maintain or extend. The code above is a combination of custom events (<em>delegates</em>), work simulation, generic and standard events.</p><p>When running that code, we will get the following.</p><pre><code class="language-text">==================================================
Processing Order: ORD-001
==================================================
  → Validating order ORD-001
    ✓ Validated: ORD-001
  → Processing payment...
    ✓ Order ORD-001: $99.99 - Completed
    ✓ All processing completed!
==================================================

==================================================
Processing Order: ORD-002
==================================================
  → Validating order ORD-002
    ✓ Validated: ORD-002
  → Processing payment...
    ✓ Order ORD-002: $149.50 - Completed
    ✓ All processing completed!
==================================================</code></pre><p>That is how easily you can build an order processor. No custom packages, etc... everything straight out of the C# language and .NET framework. It's clean and powerful. 😎</p><h2 id="my-best-practices-for-raising-events">My Best Practices for Raising Events</h2><p>I always follow this pattern when I am raising events in any application I am a part of.</p><pre><code class="language-csharp">protected virtual void OnEventName(EventArgs e)
{
    // 1. Make it virtual so derived classes can override
    // 2. Use null-conditional operator for safety
    // 3. Pass 'this' as sender to identify event source
    EventName?.Invoke(this, e);
}</code></pre><p>I am using the <code>virtual</code> keyword to allow derived classes to do the following.</p><ul><li>Add their own custom behavior before or after raising the event.</li><li>Stop the event from being raised under certain conditions.</li><li>Modify the event arguments.</li></ul><h2 id="when-am-i-using-each-of-the-event-types">When am I Using Each of The Event Types?</h2><p>I know it can seem a bit confusing with custom, generic, non-generic, etc... Let's break them down and I will tell you what I am using for what/when.</p><h3 id="custom-delegate-event">Custom Delegate + Event</h3><p>Use this when you are in a need of a super specific signature that doesn't fit the standard pattern.</p><pre><code class="language-csharp">public delegate void ProgressDelegate(int percent, string message);
public event ProgressDelegate? Progress;</code></pre><h3 id="eventhandlert">EventHandler&lt;T&gt;</h3><p>This is the one I am using for 95% of my cases. It is also the recommended approach.</p><pre><code class="language-csharp">public event EventHandler&lt;OrderProcessedEventArgs&gt;? OrderProcessed;</code></pre><h3 id="eventhandler">EventHandler</h3><p>This one should be used when you do not need to provide any custom data for the event.</p><pre><code class="language-csharp">public event EventHandler? Completed;</code></pre><h2 id="key-takeaways">Key Takeaways</h2><p>Summing it all down. An event in C# is an encapsulated delegate that provide a safe and managed way for objects to communicate. When you need to pass data along your events, use the EventArgs, and utilize EventHandler&lt;T&gt; as the way for declaring your events.</p><p>Best practice is to follow the conventional <code>OnEventName()</code> method when you are raising new events. Mark the event methods virtual to provide support for extensibility in any derived class. Always use the null-conditional operator (<code>.?</code>) when raising an event and that you can only raise an event in the class that declared it.</p><h2 id="whats-next">What's Next?</h2><p>That was quite a bit, but awesome that you made it to the end! Give yourself a high-five on that 🎉 Now that you know how to create and raise events, I think it is time to have a look at how we can subscribe to them.</p><p>In the next section we will take a closer look at how we properly subscribe and unsubscribe to events. I will also give you an introduction on how to avoid some common pitfalls (that <em>I personally got into when I first started making publisher-subscriber apps</em>) - especially memory leaks are one that many stumble upon.</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Understanding Delegates in C#]]></title>
                    <description><![CDATA[In this section you will learn what delegates are all about, why they are so super useful, and how you can use them in an effective way in your own applications.]]></description>
                    <link>https://christian-schou.com/docs/advanced-csharp-features/understanding-delegates/</link>
                    <guid isPermaLink="false">69178396214bae0001e00757</guid>

                        <category><![CDATA[Advanced C# Features]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Sat, 15 Nov 2025 05:10:32 +0100</pubDate>


                    <content:encoded><![CDATA[<p>Ever heard of delegates? If you ask me I would say it is one of the most powerful features in C# and really opened my eyes for the language when I first found out about them.</p><p>Yeah... they can be confusing for anyone new to C# I know that, but stay calm and follow along. I promise you will be eager to implement them after these three short posts.</p><h2 id="what-are-delegates">What Are Delegates?</h2><p>So.. what is it all about? A simple explanation would be to say that a delegate is a type-safe function pointer or a reference type and all it is doing is holding a reference to a method.</p><p>You can think of a delegate as a contract that dictates/defines/says how or what a method should look like (a kind of a signature), and then you can pass around references to methods that match that contract. Smart right? 🔥</p><p>In other words - a delegate makes it possible to "treat" your methods as variables that can be passed as a parameter, kept in a collection or invoked whenever you need it.</p><h2 id="why-use-delegates">Why Use Delegates?</h2><p>Delegates will give you several great things like:</p><ul><li><strong>Callback mechanisms</strong>. These will make it possible to execute code at specific points without tight coupling. What's not to like!? 👍</li><li><strong>Event handling</strong>. This is the foundation for the event-driven programming model in .NET/C#.</li><li><strong>Flexibility</strong>. You can pass different behaviors to methods at runtime like i mentioned above.</li><li><strong>Decoupling</strong>. This will make it possible to separate the caller from the implementation details.</li></ul><h2 id="how-to-declare-a-custom-delegate">How to Declare a Custom Delegate?</h2><p>Okay, I promised to give you some simple and practical examples. Let's start with the bare minimum or should we say basics. This is how you declare a custom delegate in C#.</p><pre><code class="language-csharp">public delegate bool SendConfirmationDelegate(string message);
public delegate void TaskCompletedDelegate();</code></pre><p>What is going on? 🤔</p><ul><li>The <code>delegate</code> keyword defines that this is a delegate type.</li><li><code>bool</code> or <code>void</code> is your return type.</li><li>The <code>SendConfirmationDelegate</code> name is the name of the delegate.</li><li>A as we know it from methods, the <strong>parameters</strong> are defined in <strong>parentheses</strong>.</li></ul><p>So... above are two new delegates. They create new types that can reference any method that has a matching signature</p><p>These declarations create new types that can reference any method with a matching signature. Remember the signature I mentioned earlier?</p><h2 id="real-world-examplea-notification-system">Real-World Example - A Notification System</h2><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-text">I learn by seeing some real examples, and not just a snip of some code, and then the person behind the documentation expects that I totally get it. If you are like me, you have come to the right place.</div></div><p>Let's create a real-world example. I will now show you how to build a notification system in .NET.</p><h3 id="declaring-the-delegates">Declaring The Delegates</h3><pre><code class="language-csharp">public class NotificationService
{
    // Declare delegate types
    public delegate bool SendMessageDelegate(string message);
    public delegate void TaskCompletedDelegate();

    public void ProcessNotification(
        string message,
        SendMessageDelegate sendMessage,
        TaskCompletedDelegate onComplete = null)
    {
        Console.WriteLine("Processing notification...");
        
        // Invoke the send message delegate
        bool success = sendMessage?.Invoke(message) ?? false;
        
        if (success)
        {
            Console.WriteLine("✓ Message sent successfully");
        }
        else
        {
            Console.WriteLine("✗ Failed to send message");
        }
        
        // Invoke completion callback
        onComplete?.Invoke();
    }
}</code></pre><p>That code look pretty simple. Did you notice the null-conditional operator <code>?.</code>?That makes it possible for us to safely invoke the delegate only if it's not null.</p><h3 id="using-the-notification-service">Using the Notification Service</h3><p>Okay, we have the implementation in place for the notification system. Let's now take a look at how to use it from different methods.</p><pre><code class="language-csharp">var notificationService = new NotificationService();

// Send via Email
notificationService.ProcessNotification(
    message: "Your order has shipped!",
    sendMessage: SendEmail,
    onComplete: LogCompletion);

bool SendEmail(string message)
{
    Console.WriteLine($"📧 Email sent: {message}");
    return true;
}

void LogCompletion()
{
    Console.WriteLine("✓ Notification logged");
}

// Send via SMS using lambda expressions
notificationService.ProcessNotification(
    message: "Your package arrives tomorrow",
    sendMessage: (msg) =&gt; 
    {
        Console.WriteLine($"📱 SMS sent: {msg}");
        return true;
    },
    onComplete: () =&gt; Console.WriteLine("✓ SMS notification complete"));</code></pre><p>Did you just see a bright light? 💡 😅</p><p>The two examples above shows how much power the delegates gives us. The NotificationService is not aware or actually doesn't evene care about how the messages are sent.</p><p>This gives you the option to plug-and-play new options for notifications, like:</p><ul><li>SMS</li><li>Emails</li><li>Push Notifications</li><li>etc...</li></ul><h2 id="multicast-delegatesnotify-multiple-recipients">Multicast Delegates - Notify Multiple Recipients</h2><p>If you got amazed or happy about the examples above due to the power you are given, you are going to be super happy now! 😄 Delegates can hold references to multiple methods 🔥 This is referred to as a <strong>multicast delegate</strong>.</p><h3 id="creating-a-logger-with-multiple-destinations">Creating a logger with multiple destinations</h3><p>Let's take a practical example that I often practice in API's and systems in general. Logging to multiple destinations, but keeping it simple and maintainable for everyone.</p><pre><code class="language-csharp">public class Logger
{
    // Delegate that takes a message
    public delegate void LogMessageDelegate(string message);
    
    public void LogImportantEvent(string message)
    {
        // Create a multicast delegate
        LogMessageDelegate logger = WriteToConsole;
        logger += WriteToFile;
        logger += SendToMonitoring;
        
        // Single invocation calls all three methods
        Console.WriteLine("Logging event to all destinations...");
        logger(message);
    }
    
    private void WriteToConsole(string message)
    {
        Console.WriteLine($"[Console] {message}");
    }
    
    private void WriteToFile(string message)
    {
        Console.WriteLine($"[File] {message}");
    }
    
    private void SendToMonitoring(string message)
    {
        Console.WriteLine($"[Monitoring] {message}");
    }
}

// Usage
var logger = new Logger();
logger.LogImportantEvent("System started successfully");

/* Output:
Logging event to all destinations...
[Console] System started successfully
[File] System started successfully
[Monitoring] System started successfully
*/</code></pre><p>When you call/invoke a multicast delegate, all the methods you have declared in the delegate are called in the order they have been defined. Awesome right?! 🙌</p><p>A few things I would mention, that I think are crucial or important to know are:</p><ul><li>If you delegate has a return type, you will only get the value from the last method that was invoked in the delegate.</li><li>If any of the methods in your delegate throws an exception, any upcoming/next methods won't be executed/invoked.</li><li>You can use the <code>+=</code> operator to add methods or <code>-=</code> to remove them again.</li></ul><h2 id="lambda-expressions-with-delegates">Lambda Expressions with Delegates</h2><p>Lambda expressions is something most devs knows. Especially if you have been working with LINQ 😄 Using lambdas will make your code for the delegates more concise.</p><p>Let's take a look at a practical example where we will create a very simple payment processor.</p><pre><code class="language-csharp">public class PaymentProcessor
{
    public delegate void PaymentDelegate(decimal amount);
    
    public void ProcessPayment(decimal amount, PaymentDelegate processMethod)
    {
        Console.WriteLine($"Processing ${amount}...");
        processMethod(amount);
    }
}

// Usage with different payment methods
var processor = new PaymentProcessor();

// Traditional method reference
processor.ProcessPayment(100.00m, ChargeCreditCard);

void ChargeCreditCard(decimal amount)
{
    Console.WriteLine($"💳 Charged ${amount} to credit card");
}

// Lambda expression - inline
processor.ProcessPayment(50.00m, (amount) =&gt;
{
    Console.WriteLine($"💰 Processed ${amount} via PayPal");
});

// Lambda expression - single line
processor.ProcessPayment(25.00m, amt =&gt; Console.WriteLine($"📱 Processed ${amt} via Apple Pay"));</code></pre><p>As you can see, lambda expressions are very useful if you have some simple, one-off logic that you don't need to reuse.</p><h2 id="practical-exampledata-processing-pipeline">Practical Example - Data Processing Pipeline</h2><p>Let's take another round looking at a practical example. In this example I will show you how to create a flexible data processing setup using delegates.</p><pre><code class="language-csharp">public class DataProcessor
{
    public delegate bool ValidationDelegate(string data);
    public delegate string TransformDelegate(string data);
    
    public void ProcessData(
        string input,
        ValidationDelegate validator,
        TransformDelegate transformer)
    {
        Console.WriteLine($"Input: {input}");
        
        // Validate
        if (!validator(input))
        {
            Console.WriteLine("❌ Validation failed");
            return;
        }
        
        // Transform
        string result = transformer(input);
        Console.WriteLine($"Output: {result}");
    }
}

// Usage
var processor = new DataProcessor();

processor.ProcessData(
    input: "hello world",
    validator: (data) =&gt; !string.IsNullOrEmpty(data),
    transformer: (data) =&gt; data.ToUpper());

processor.ProcessData(
    input: "test@christian-schou.com",
    validator: (email) =&gt; email.Contains("@"),
    transformer: (email) =&gt; $"User: {email.Split('@')[0]}");

/* Output:
Input: hello world
Output: HELLO WORLD

Input: test@christian-schou.com
Output: User: test
*/</code></pre><h2 id="two-ways-to-invoke-delegates">Two Ways to Invoke Delegates</h2><p>Delegates can be invokes in two ways in C#, this is how you can do it.</p><pre><code class="language-csharp">public delegate void MessageDelegate(string msg);

MessageDelegate handler = (msg) =&gt; Console.WriteLine(msg);

// Method 1: Direct invocation
handler("Hello");

// Method 2: Using Invoke method (safer with null checking)
handler?.Invoke("World");</code></pre><p>Are there any difference? Nope 😎 Both ways gives you the same result. <strong>I prefer option two</strong>, where you explicitly use the <code>Invoke()</code> method as it is more descriptive and works great in condition with null-conditional operators.</p><h2 id="building-a-simple-task-scheduler">Building a Simple Task Scheduler</h2><p>Okay, let's take another practical example that a lot of applications often has some sort of, a task scheduler. This one will be my last practical example in this post, and will tie everything together that I have explained and showed above.</p><pre><code class="language-csharp">public class TaskScheduler
{
    public delegate void TaskDelegate(string taskName);
    
    public void RunTasks(params (string name, TaskDelegate task)[] tasks)
    {
        Console.WriteLine($"Running {tasks.Length} tasks...\n");
        
        foreach (var (name, task) in tasks)
        {
            Console.WriteLine($"Executing: {name}");
            task?.Invoke(name);
            Console.WriteLine();
        }
        
        Console.WriteLine("All tasks completed!");
    }
}

// Usage
var scheduler = new TaskScheduler();

scheduler.RunTasks(
    ("Backup Database", (name) =&gt; Console.WriteLine($"  ✓ {name} completed")),
    ("Send Reports", (name) =&gt; Console.WriteLine($"  ✓ {name} sent to admin")),
    ("Clear Cache", (name) =&gt; Console.WriteLine($"  ✓ {name} cleared successfully"))
);

/* Output:
Running 3 tasks...

Executing: Backup Database
  ✓ Backup Database completed

Executing: Send Reports
  ✓ Send Reports sent to admin

Executing: Clear Cache
  ✓ Clear Cache cleared successfully

All tasks completed!
*/</code></pre><p>When building applications, it is very common practice to run small tasks in sequence. This could be stuff like making a backup, sending a data report, etc... In my example above, we are using the built-in task scheduler in .NET and it accepts any number of taks, that each has a name a small amount of code to execute.</p><p>Building you applications like I have shown above will make your code easy to read, easy to extend and let it stay organized. This way it won't be a nightmare to introduce another step in your sequence of tasks that goes into making a backup.</p><p>The final result using delegates, and not just in the example above, but in any application is a clean and super flexible way to chain operations.</p><h2 id="key-takeaways">Key Takeaways</h2><p>As I mentioned at the very beginning, delegates in C# is a type-safe way of referencing methods. They will make your applications flexible as they allow you to pass behavior as parameters - that is pretty cool right?! 🔥 If you ask me, it's the perfect choice if you want to make your code clean and modular at the same time without reinventing the wheel. One very nice feature I really like are the multicast delegates. They allows us to hold multiple methods and trigger all of them in one go.</p><p>If you are looking for a quick and readable delegate, stick to the lambda expressions. They will make your code concise and easy to maintain. I have been asked quite a few times about the usage of the null-conditional operator, and I always tell developers that it is good practice to use it, as it will prevent null reference exceptions when invoking the delegates.</p><h2 id="whats-next">What's Next?</h2><p>This post was about delegates. Now I think it is time for you to take a look at events. Events build upon delegates (see what I did there), and they will give us even more power in C# to create e.g. a publish-subscribe pattern.</p><h2 id="resources">Resources</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Work with delegate types in C# - C#</div><div class="kg-bookmark-description">Explore delegate types in C#. A delegate is a date type that refers to a method with a defined parameter list and return type. You use delegates to pass methods as arguments to other methods.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-23.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">BillWagner</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/logo_csharp-4.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/delegates?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Delegates - C# language specification</div><div class="kg-bookmark-description">This chapter defines delegates, which are objects that hold type safe function pointers.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-24.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">BillWagner</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/dotnet-logo-3.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Advanced C# Features]]></title>
                    <description><![CDATA[C#’s most powerful features! Explained simply, demonstrated clearly, and organized for quick learning.]]></description>
                    <link>https://christian-schou.com/collections/advanced-csharp-features/</link>
                    <guid isPermaLink="false">69177ced214bae0001e00716</guid>

                        <category><![CDATA[Advanced C# Features]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Fri, 14 Nov 2025 20:05:10 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2025/11/brain-duotone.svg" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2025/11/brain-duotone.svg" alt="Advanced C# Features"/> <p>Are you ready to get even better at <strong>C#</strong>? In this collection I am breaking down 17 advanced, but awesome features of C# for you.</p><p>When you have finished reading/watching all of them, you have been introduced to practical and simple examples with explanation of some cool features like:</p><ul><li><a href="https://christian-schou.com/collections/advanced-csharp-features/understanding-events/" rel="noreferrer">Events</a></li><li><a href="https://christian-schou.com/collections/advanced-csharp-features/understanding-delegates/" rel="noreferrer">Delegates</a></li><li>Records</li><li>Pattern Matching</li><li>Span&lt;T&gt;</li><li>And much much more...</li></ul>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[C# for Beginners]]></title>
                    <description><![CDATA[C# is a powerful language that works on many platforms. You can use it to create various applications, including mobile apps for Windows, Android, and iOS, as well as games, websites, and desktop software.

Once you grasp the basics of C# and .NET development, many opportunities open up. Whether you]]></description>
                    <link>https://christian-schou.com/courses/csharp-for-beginners/</link>
                    <guid isPermaLink="false">68dc4ac1214bae0001e005a2</guid>

                        <category><![CDATA[C# Fundamentals (C#)]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Tue, 30 Sep 2025 23:35:15 +0200</pubDate>


                    <content:encoded><![CDATA[<p>C# is a powerful language that works on many platforms. You can use it to create various applications, including mobile apps for Windows, Android, and iOS, as well as games, websites, and desktop software.<br><br>Once you grasp the basics of C# and .NET development, many opportunities open up. Whether you want to build mobile apps, move into web development, or explore other areas, having solid fundamentals makes it easier to shift between technology stacks.<br><br>In this course, I will teach you the fundamentals of C# programming in a clear and straightforward way. You’ll go beyond just syntax; you’ll learn best practices and techniques that can boost your growth as a developer.<br><br>Each section includes focused video lectures and hands-on coding exercises. These exercises combine academic concepts with real-world situations, helping you build strong problem-solving skills. To become a successful programmer with high-paying job options and career flexibility, you need to think like a programmer. These exercises are designed to help with that. Each exercise comes with solutions, so you can compare your approach with mine and find areas to improve.<br><br>While free tutorials may teach C#, this course provides a deeper understanding. If you want to be a well-rounded developer with solid job prospects, this course is for you. For each topic, you will learn not just what to do, but also why and how. You will encounter common errors that often happen in real C# development, complete with detailed explanations and solutions to help you troubleshoot effectively.</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Validate Settings Using The IOptions Pattern in .NET]]></title>
                    <description><![CDATA[Learn how you can bind values from your appsettings.json or environment to a settings class with the options-pattern in .NET and validate them.]]></description>
                    <link>https://christian-schou.com/blog/validate-settings-with-ioptions-pattern-dotnet/</link>
                    <guid isPermaLink="false">67f64f8c88f8f30001d55ab6</guid>

                        <category><![CDATA[.NET]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Tue, 13 May 2025 21:02:21 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2025/05/validate-settings-with-ioptions-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2025/05/validate-settings-with-ioptions-twc.webp" alt="Validate Settings Using The IOptions Pattern in .NET"/> <p>We have all been there 😅 configuration options or settings that we needed in our applications, but they were not available or in the wrong format. In this quick tutorial I am going to show you how easy it is to add validation to your options/settings classes using <code>IOptions</code>.</p><p>With the <strong>options-pattern</strong>, we can write strongly typed options/settings that our application can use at runtime. This makes our code more robust as we become type-safe.</p><p>The only problem I have encountered a lot of times with users of my code is that they write invalid values for their option fields. It tends to cause a lot of problems as the code tries to parse and use them, and that results in errors as they are incorrect.</p><p>So... I thought it would be nice to have a quick tutorial that showcases how easily we can get around that problem. It is actually fairly simple to solve that problem and make sure the users of your software are using it in the intended way.</p><h2 id="%F0%9F%99%8B-what-is-a-strongly-typed-configuration">🙋 What is a strongly typed configuration?</h2><p>A strongly typed configuration is a class where we define each settings/options property inside. Let's say I wanted to integrate my app with the <a href="https://marketstack.com/?ref=christian-schou.com" rel="noreferrer">Marketstack</a> <a href="https://marketstack.com/documentation_v2?ref=christian-schou.com" rel="noreferrer">API</a>, I could create a <code>MarketstackSettings</code> <strong>class</strong> that would contain the required details.</p><pre><code class="language-csharp">public sealed class MarketstackSettings
{
  public string AccessKey { get; init; }
}</code></pre><p>This of course has to be defined inside <code>appsettings.json</code> for the application, so that we can get the values for our configuration of the app.</p><pre><code class="language-json">"MarketstackSettings" {
  "AccessKey" : "YOUR_ACCESS_KEY"
}</code></pre><p>If you want a <strong>key</strong>, you can get it by signing up at the link below.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://marketstack.com/signup/free?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Sign Up - marketstack</div><div class="kg-bookmark-description">Sign up for a marketstack account and get instant access to market data for 125,000+ stock tickers, 72 global stock exchanges and 30 years of historical data.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/marketstack_icon-1.png" alt=""><span class="kg-bookmark-author">marketstack Logo</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/marketstack_logo_white-1.svg" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>Alright! Now we simply need to configure the <code>MarketstackSettings</code> at the startup of our app.</p><pre><code class="language-csharp">builder.Services.Configure&lt;MarketstackSettings&gt;(
  builder.Configuration.GetSection(nameof(MarketstackSettings)));</code></pre><p>This makes it able for us to <strong>inject</strong> the configuration values for that settings class using <code>IOptions&lt;MarketstackSettings&gt;()</code>.</p><pre><code class="language-csharp">public class MarketstackService : IMarketstackService
{
  private readonly MarketstackSettings _marketstackSettings;

  public class MarketstackService(IOptions&lt;MarketstackSettings&gt; marketstackSettings)
  {
    _marketstackSettings = marketstackSettings.Value;
  }

  public void GetMarketData()
  {
    var accessToken = _marketstackSettings.AccessKey;

    ...
  }
}</code></pre><h2 id="%F0%9F%8E%AF-why-isnt-that-a-bullet-proof-solution">🎯 Why isn't that a bullet-proof solution?</h2><p>Let's say we would ship our app with the configuration of our market settings like I did above. That would make the user of the app responsible for entering the correct information in the configuration of the app.</p><p>Many developers would have no problem doing this if they carefully read the documentation and understood what was expected by the app at startup. But from experience I just know that most engineers skip where they can (<em>not because they are dumb, but because they are lazy and will deal with issues like this later</em>). So... how can we solve that problem? 😄 </p><p>We could use validation for the configuration properties ⚙️</p><h2 id="%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB-using-validation-with-the-options-pattern">🧑‍💻 Using validation with the options-pattern</h2><p>Luckily for us, .NET provides an easy way of performing <strong>validation</strong> for the properties we have in the settings class. All we have to do is add <strong>data annotations</strong> on the properties.</p><p>Let's say we wanted to make the <code>AccessKey</code> <strong>required</strong>. All we have to do is add the <code>[Required]</code> attribute on the property.</p><pre><code class="language-csharp">public sealed class MarketstackSettings
{
  [Required]
  public string AccessKey { get; init; }
}</code></pre><p>Now for the configuration part, we need to tell .NET that we want to validate the data annotations on the properties.</p><p>It's a minor change. Delete the configuration from before and update it to the following:</p><pre><code class="language-csharp">builder.Services
  .AddOptions&lt;MarketstackSettings&gt;()
  .BindConfiguration(nameof(MarketstackSettings))
  .ValidateDataAnnotations();</code></pre><p>What did we just do now? 🤔</p><ul><li><code>AddOptions&lt;MarketstackSettings&gt;()</code> will return <code>OptionsBuilder&lt;MarketstackSettings&gt;()</code> that will bind to our <code>MarketstackSettings</code> class.</li><li><code>BindConfiguration(nameof(MarketstackSettings))</code> will make sure to bind the actual values from our configuration file <code>appsettings.json</code>.</li><li><code>ValidateDataAnnotations()</code> will <strong>enable</strong> validation by using the added <strong>data annotations</strong> on the properties in our settings class. Here it will make sure there is a value in the <code>AccessKey</code> property as we specified that it was required.</li></ul><p>Now if you try to <strong>inject</strong> the <code>MarketstackSettings</code> and the <code>AccessKey</code> is missing, we would encounter a <strong>runtime exception</strong>. Awesome! ✌️</p><h2 id="%F0%9F%9A%80-validate-settings-at-startup">🚀 Validate settings at startup</h2><p>What if I told you that it is possible to validate your configuration properties at startup to avoid runtime exceptions later down the road? 😎</p><p>It is totally possible, and all you have to do is call <code>ValidateOnStart()</code> when you configure the settings.</p><pre><code class="language-csharp">builder.Services
  .AddOptions&lt;MarketstackSettings&gt;()
  .BindConfiguration(nameof(MarketstackSettings))
  .ValidateDataAnnotations()
  .ValidateOnStart();</code></pre><p>Now our application will throw an exception when starting up if the <code>AccessKey</code> is missing during startup.</p><h2 id="%F0%9F%93%9D-summary">📝 Summary</h2><p>In this short tutorial you learned how to use the options-pattern in .NET for retrieving configuration values from appsettings.json and bind them to your configuration classes.</p><p>You also learned how to validate the data annotations on the settings classes from the option values, and do it at the startup of your application. This should help you minimize the number of runtime errors or exceptions that could happen.</p><p>If you got any questions, please leave a comment below. Until next time - happy coding! ✌️</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Avoid Complexity with the State Design Pattern in .NET]]></title>
                    <description><![CDATA[Struggling with complex if-else chains in your growing codebase? Let me show you how the state design pattern can simplify logic, boost maintainability, and make your code easier to scale—with a practical, real-world example of a document flow.]]></description>
                    <link>https://christian-schou.com/blog/avoid-complexity-with-the-state-design-pattern-in-net/</link>
                    <guid isPermaLink="false">681b07510579460001119979</guid>

                        <category><![CDATA[.NET]]></category>
                        <category><![CDATA[Design Patterns]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Tue, 06 May 2025 21:24:00 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2025/05/avoid-complexity-with-state-design-pattern-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2025/05/avoid-complexity-with-state-design-pattern-twc.webp" alt="Avoid Complexity with the State Design Pattern in .NET"/> <p>I am reading a lot of code on the internet in open-source repositories, when making reviews on pull-requests and understanding other developers' code. One thing that I often see is code full of <code>if-else</code> statements checking tons of various states 😅</p><p>It's fine when we got a small application doing a simple operation/task, but imagine a codebase that is growing. If your code has a lot of conditional branches (<em>if-else statements</em>), it can be hard to read and understand by others (<em>or yourself in half a year, when you need to refactor something</em>).</p><p><strong>How can we solve it?</strong> That is what I am going to show you in this article about the <strong>state design pattern</strong>. It will help you implement a more maintainable, extensible and testable code base in your projects. ✌️</p><p>I will show you a practical example of how to do it. More specifically, we will be building a document processing system, where the documents will go through various transitions and change state from e.g. draft to review, approved and finally published. 🔥</p><p>Before I show the good stuff, I want to briefly tell you a little about the state design pattern and what the real problem is with conditional logic. If you are ready, let's get to it.</p><h2 id="%F0%9F%92%A1-what-is-the-state-design-pattern">💡 What is the State Design Pattern?</h2><p>So... What is it really all about? The State Design Pattern is <strong>a behavioral design pattern</strong> that <strong>allows an object to alter its behavior when its internal state changes</strong>. It will do it by making an object change its class, providing a clean way to organize your code that has state-dependent behavior. 💪</p><p>Some <strong>key takeaways</strong> about the state pattern that I want you to know are 🧠</p><ul><li>The class that contains the state and delegates state-specific behavior to the current state object is what we refer to as <strong>context</strong>.</li><li>A common interface for all concrete states is defined as a <strong>state interface</strong>.</li><li>Classes that implement the state interface and provide the behavior associated with a particular state are what we refer to as <strong>concrete states</strong>.</li></ul><h2 id="%F0%9F%A4%94-the-problem-with-conditional-logic">🤔 The Problem with Conditional Logic</h2><p>Before I show you how to use the state design pattern, I would like to address the problem with excessive use of conditional logic in our software.</p><p>To demonstrate is with code, I have written a simple program for the Document transition that I will be fixing later. When a new state or behavior is added, the code-base grows exponentially, which is the big problem.</p><p>Another thing that you should consider is that I am breaking the single responsibility principle as I make my class handle all business logic for the states. While adding new states, it requires us to modify existing code, which is a violation of the open/closed principle. You can read more about SOLID in the article below.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://christian-schou.com/blog/what-are-the-solid-principles/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">SOLID Principles - The art of writing maintainable code 👨‍💻</div><div class="kg-bookmark-description">Learn what the SOLID principles are about and how you can use them to write clean and maintainable code in your applications. With C# examples.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/cropped-cs-logo-color-retina-6.png" alt=""><span class="kg-bookmark-author">Christian Schou Køster</span><span class="kg-bookmark-publisher">Christian Schou Køster</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/solid-principles-1.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>And the last but not least thing is that testing our code (which is crucial) gets more complex, at you would have to test all conditional branches separately. Let's take a look at it, shall we 😄</p><pre><code class="language-csharp">public class Document
{
    public enum Status
    {
        Draft,
        InReview,
        Approved,
        Published
    }

    public Status CurrentStatus { get; private set; } = Status.Draft;
    
    public string Content { get; set; }
    
    public void Edit(string newContent)
    {
        if (CurrentStatus == Status.Draft)
        {
            Content = newContent;
        }
        else if (CurrentStatus == Status.InReview)
        {
            throw new InvalidOperationException("Cannot edit document in review");
        }
        else if (CurrentStatus == Status.Approved)
        {
            throw new InvalidOperationException("Cannot edit approved document");
        }
        else if (CurrentStatus == Status.Published)
        {
            throw new InvalidOperationException("Cannot edit published document");
        }
    }
    
    public void Submit()
    {
        if (CurrentStatus == Status.Draft)
        {
            CurrentStatus = Status.InReview;
        }
        else if (CurrentStatus == Status.InReview)
        {
            throw new InvalidOperationException("Document is already in review");
        }
        // More conditionals...
    }
    
    public void Approve()
    {
        if (CurrentStatus == Status.InReview)
        {
            CurrentStatus = Status.Approved;
        }
        else if (CurrentStatus == Status.Draft)
        {
            throw new InvalidOperationException("Document must be reviewed before approval");
        }
        // More conditionals...
    }
    
    public void Publish()
    {
        // Even more conditionals...
    }
}</code></pre><p>Can you see the problems? We will end up breaking the SOLID principles and have a codebase growing exponentially and getting more complex with each addition. Let's fix that! 💪</p><h2 id="%E2%9C%8D%EF%B8%8F-visualizing-the-state-pattern-for-our-solution">✍️ Visualizing the State Pattern for our solution</h2><p>Below is a class diagram that visualizes the state pattern structure for our document transitioning app in this article. It will be totally different from yours as it all depends on your requirements for the application you are building.</p>
<!--kg-card-begin: html-->
<svg aria-roledescription="class" role="graphics-document document" viewBox="0 0 1026.8671875 827" style="max-width: 3840px; background-color: transparrent; max-height: 3840px;" class="classDiagram" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="100%" id="my-svg"><style>#my-svg{font-family:arial,sans-serif;font-size:14px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#my-svg .error-icon{fill:#ffffff;}#my-svg .error-text{fill:#000000;stroke:#000000;}#my-svg .edge-thickness-normal{stroke-width:2px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:#000000;stroke:#000000;}#my-svg .marker.cross{stroke:#000000;}#my-svg svg{font-family:arial,sans-serif;font-size:14px;}#my-svg p{margin:0;}#my-svg g.classGroup text{fill:#000000;stroke:none;font-family:arial,sans-serif;font-size:10px;}#my-svg g.classGroup text .title{font-weight:bolder;}#my-svg .nodeLabel,#my-svg .edgeLabel{color:#333;}#my-svg .noteLabel .nodeLabel,#my-svg .noteLabel .edgeLabel{color:#333;}#my-svg .edgeLabel .label rect{fill:#ffffff;}#my-svg .label text{fill:#333;}#my-svg .labelBkg{background:#ffffff;}#my-svg .edgeLabel .label span{background:#ffffff;}#my-svg .classTitle{font-weight:bolder;}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#ffffff;stroke:#000000;stroke-width:2;}#my-svg .divider{stroke:#000000;stroke-width:1;}#my-svg g.clickable{cursor:pointer;}#my-svg g.classGroup rect{fill:#ffffff;stroke:#000000;}#my-svg g.classGroup line{stroke:#000000;stroke-width:1;}#my-svg .classLabel .box{stroke:none;stroke-width:0;fill:#ffffff;opacity:0.5;}#my-svg .classLabel .label{fill:#000000;font-size:10px;}#my-svg .relation{stroke:#000000;stroke-width:2;fill:none;}#my-svg .dashed-line{stroke-dasharray:3;}#my-svg .dotted-line{stroke-dasharray:1 2;}#my-svg #compositionStart,#my-svg .composition{fill:#000000!important;stroke:#000000!important;stroke-width:1;}#my-svg #compositionEnd,#my-svg .composition{fill:#000000!important;stroke:#000000!important;stroke-width:1;}#my-svg #dependencyStart,#my-svg .dependency{fill:#000000!important;stroke:#000000!important;stroke-width:1;}#my-svg #dependencyStart,#my-svg .dependency{fill:#000000!important;stroke:#000000!important;stroke-width:1;}#my-svg #extensionStart,#my-svg .extension{fill:transparent!important;stroke:#000000!important;stroke-width:1;}#my-svg #extensionEnd,#my-svg .extension{fill:transparent!important;stroke:#000000!important;stroke-width:1;}#my-svg #aggregationStart,#my-svg .aggregation{fill:transparent!important;stroke:#000000!important;stroke-width:1;}#my-svg #aggregationEnd,#my-svg .aggregation{fill:transparent!important;stroke:#000000!important;stroke-width:1;}#my-svg #lollipopStart,#my-svg .lollipop{fill:#ffffff!important;stroke:#000000!important;stroke-width:1;}#my-svg #lollipopEnd,#my-svg .lollipop{fill:#ffffff!important;stroke:#000000!important;stroke-width:1;}#my-svg .edgeTerminals{font-size:11px;line-height:initial;}#my-svg .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#my-svg .edgeLabel{background-color:hsl(-120, 0%, 80%);text-align:center;}#my-svg .edgeLabel p{background-color:hsl(-120, 0%, 80%);}#my-svg .edgeLabel rect{opacity:0.5;background-color:hsl(-120, 0%, 80%);fill:hsl(-120, 0%, 80%);}#my-svg .node .neo-node{stroke:#000000;}#my-svg [data-look="neo"].node rect,#my-svg [data-look="neo"].cluster rect,#my-svg [data-look="neo"].node polygon{stroke:url(#my-svg-gradient);filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg [data-look="neo"].node path{stroke:url(#my-svg-gradient);stroke-width:2;}#my-svg [data-look="neo"].node .outer-path{filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg [data-look="neo"].node .neo-line path{stroke:#000000;filter:none;}#my-svg [data-look="neo"].node circle{stroke:url(#my-svg-gradient);filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg [data-look="neo"].node circle .state-start{fill:#000000;}#my-svg [data-look="neo"].statediagram-cluster rect{fill:#ffffff;stroke:url(#my-svg-gradient);stroke-width:2;}#my-svg [data-look="neo"].icon-shape .icon{fill:url(#my-svg-gradient);filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg [data-look="neo"].icon-shape .icon-neo path{stroke:url(#my-svg-gradient);filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="18" class="marker aggregation class" id="my-svg_class-aggregationStart"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker aggregation class" id="my-svg_class-aggregationEnd"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker markerUnits="userSpaceOnUse" orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="15" class="marker aggregation class" id="my-svg_class-aggregationStart-margin"><path style="stroke-width: 2;" d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker markerUnits="userSpaceOnUse" orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker aggregation class" id="my-svg_class-aggregationEnd-margin"><path style="stroke-width: 2;" d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker markerUnits="userSpaceOnUse" orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="17.5" class="marker extension class" id="my-svg_class-extensionStart"><path d="M 1,7 L18,13 V 1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker extension class" id="my-svg_class-extensionEnd"><path d="M 1,1 V 13 L18,7 Z"/></marker></defs><marker viewBox="0 0 20 14" markerUnits="userSpaceOnUse" orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="18" class="marker extension class" id="my-svg_class-extensionStart-margin"><polygon style="stroke-width: 2; stroke-dasharray: 0;" points="10,7 18,13 18,1"/></marker><defs><marker viewBox="0 0 20 14" markerUnits="userSpaceOnUse" orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="9" class="marker extension class" id="my-svg_class-extensionEnd-margin"><polygon style="stroke-width: 2; stroke-dasharray: 0;" points="10,1 10,13 18,7"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="18" class="marker composition class" id="my-svg_class-compositionStart"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="1" class="marker composition class" id="my-svg_class-compositionEnd"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker markerUnits="userSpaceOnUse" orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="15" class="marker composition class" id="my-svg_class-compositionStart-margin"><path style="stroke-width: 0;" d="M 18,7 L9,13 L1,7 L9,1 Z" viewBox="0 0 15 15"/></marker></defs><defs><marker markerUnits="userSpaceOnUse" orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="3.5" class="marker composition class" id="my-svg_class-compositionEnd-margin"><path style="stroke-width: 0;" d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="6" class="marker dependency class" id="my-svg_class-dependencyStart"><path d="M 5,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="13" class="marker dependency class" id="my-svg_class-dependencyEnd"><path d="M 18,7 L9,13 L14,7 L9,1 Z"/></marker></defs><defs><marker markerUnits="userSpaceOnUse" orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="4" class="marker dependency class" id="my-svg_class-dependencyStart-margin"><path style="stroke-width: 0;" d="M 5,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker markerUnits="userSpaceOnUse" orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="16" class="marker dependency class" id="my-svg_class-dependencyEnd-margin"><path style="stroke-width: 0;" d="M 18,7 L9,13 L14,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="13" class="marker lollipop class" id="my-svg_class-lollipopStart"><circle r="6" cy="7" cx="7" fill="transparent"/></marker></defs><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="1" class="marker lollipop class" id="my-svg_class-lollipopEnd"><circle r="6" cy="7" cx="7" fill="transparent"/></marker></defs><defs><marker markerUnits="userSpaceOnUse" orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="13" class="marker lollipop class" id="my-svg_class-lollipopStart-margin"><circle stroke-width="2" r="6" cy="7" cx="7" fill="transparent"/></marker></defs><defs><marker markerUnits="userSpaceOnUse" orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="1" class="marker lollipop class" id="my-svg_class-lollipopEnd-margin"><circle stroke-width="2" r="6" cy="7" cx="7" fill="transparent"/></marker></defs><g class="root"><g class="clusters"/><g class="edgePaths"><path marker-start="url(#my-svg_class-aggregationStart-margin)" data-points="W3sieCI6NTAyLjczODI4MTI1LCJ5IjoyNDh9LHsieCI6NTAyLjczODI4MTI1LCJ5IjoyODMuNX0seyJ4Ijo1MDIuNzM4MjgxMjUsInkiOjMxOX1d" data-id="id_Document_IDocumentState_1" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 53.75 0; stroke-dashoffset: 0;;;;" class="edge-thickness-normal edge-pattern-solid relation" id="id_Document_IDocumentState_1" d="M502.73828125,265.25L502.73828125,283.5L502.73828125,319"/><path marker-start="url(#my-svg_class-extensionStart-margin)" data-points="W3sieCI6MzkwLjM2NzE4NzUsInkiOjQ3My44OTQ5ODEzMjkzNTAyM30seyJ4IjoxMTAuNDUzMTI1LCJ5Ijo1NzkuNX0seyJ4IjoxMTAuNDUzMTI1LCJ5Ijo2MTV9XQ==" data-id="id_IDocumentState_DraftState_2" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0; stroke-dashoffset: 0;;;;" class="edge-thickness-normal edge-pattern-dashed relation" id="id_IDocumentState_DraftState_2" d="M374.22762117592083,479.98406162695255L118.67805710360986,576.3969257899769Q110.453125,579.5 110.453125,588.2908234916817L110.453125,615"/><path marker-start="url(#my-svg_class-extensionStart-margin)" data-points="W3sieCI6NDAxLjI3MjExNzgyMDk0NTk0LCJ5Ijo1NDR9LHsieCI6MzY5LjI1MzkwNjI1LCJ5Ijo1NzkuNX0seyJ4IjozNjkuMjUzOTA2MjUsInkiOjYxNX1d" data-id="id_IDocumentState_ReviewState_3" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0; stroke-dashoffset: 0;;;;" class="edge-thickness-normal edge-pattern-dashed relation" id="id_IDocumentState_ReviewState_3" d="M389.7188843484022,556.809578304101L378.5882092489204,569.1506475470239Q369.25390625,579.5 369.25390625,593.4369404343843L369.25390625,615"/><path marker-start="url(#my-svg_class-extensionStart-margin)" data-points="W3sieCI6NjA0LjIwNDQ0NDY3OTA1NDEsInkiOjU0NH0seyJ4Ijo2MzYuMjIyNjU2MjUsInkiOjU3OS41fSx7IngiOjYzNi4yMjI2NTYyNSwieSI6NjE1fV0=" data-id="id_IDocumentState_ApprovedState_4" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0; stroke-dashoffset: 0;;;;" class="edge-thickness-normal edge-pattern-dashed relation" id="id_IDocumentState_ApprovedState_4" d="M615.7576781515978,556.809578304101L626.8883532510796,569.1506475470239Q636.22265625,579.5 636.22265625,593.4369404343843L636.22265625,615"/><path marker-start="url(#my-svg_class-extensionStart-margin)" data-points="W3sieCI6NjE1LjEwOTM3NSwieSI6NDcyLjU1MjEyNjEyMDkxNDA3fSx7IngiOjkwNy44NTU0Njg3NSwieSI6NTc5LjV9LHsieCI6OTA3Ljg1NTQ2ODc1LCJ5Ijo2MTV9XQ==" data-id="id_IDocumentState_PublishedState_5" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0; stroke-dashoffset: 0;;;;" class="edge-thickness-normal edge-pattern-dashed relation" id="id_IDocumentState_PublishedState_5" d="M631.3119990147634,478.4713722752999L899.6605169362244,576.5061678302928Q907.85546875,579.5 907.85546875,588.2246929052246L907.85546875,615"/></g><g class="edgeLabels"><g transform="translate(502.73828125, 283.5)" class="edgeLabel"><g transform="translate(-11.2890625, -10.5)" data-id="id_Document_IDocumentState_1" class="label"><foreignObject height="21" width="22.578125"><div class="labelBkg" xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel" style=";display: inline-block"><p>has</p></span></div></foreignObject></g></g><g transform="translate(110.453125, 579.5)" class="edgeLabel"><g transform="translate(-35.796875, -10.5)" data-id="id_IDocumentState_DraftState_2" class="label"><foreignObject height="21" width="71.59375"><div class="labelBkg" xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel" style=";display: inline-block"><p>implements</p></span></div></foreignObject></g></g><g transform="translate(369.25390625, 579.5)" class="edgeLabel"><g transform="translate(-35.796875, -10.5)" data-id="id_IDocumentState_ReviewState_3" class="label"><foreignObject height="21" width="71.59375"><div class="labelBkg" xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel" style=";display: inline-block"><p>implements</p></span></div></foreignObject></g></g><g transform="translate(636.22265625, 579.5)" class="edgeLabel"><g transform="translate(-35.796875, -10.5)" data-id="id_IDocumentState_ApprovedState_4" class="label"><foreignObject height="21" width="71.59375"><div class="labelBkg" xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel" style=";display: inline-block"><p>implements</p></span></div></foreignObject></g></g><g transform="translate(907.85546875, 579.5)" class="edgeLabel"><g transform="translate(-35.796875, -10.5)" data-id="id_IDocumentState_PublishedState_5" class="label"><foreignObject height="21" width="71.59375"><div class="labelBkg" xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel" style=";display: inline-block"><p>implements</p></span></div></foreignObject></g></g></g><g class="nodes"><g transform="translate(502.73828125, 128)" data-look="neo" data-et="node" data-node="true" data-id="Document" id="classId-Document-0" class="node default"><g class="basic label-container outer-path"><path style="" fill="#ffffff" stroke-width="0" stroke="none" d="M-121.71484375 -120 L121.71484375 -120 L121.71484375 120 L-121.71484375 120"/><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-121.71484375 -120 C-60.88354422124442 -120, -0.05224469248884134 -120, 121.71484375 -120 M-121.71484375 -120 C-29.101638368127595 -120, 63.51156701374481 -120, 121.71484375 -120 M121.71484375 -120 C121.71484375 -34.55705274858906, 121.71484375 50.885894502821884, 121.71484375 120 M121.71484375 -120 C121.71484375 -39.24587797892842, 121.71484375 41.50824404214316, 121.71484375 120 M121.71484375 120 C65.12185707252536 120, 8.528870395050703 120, -121.71484375 120 M121.71484375 120 C70.26654084272627 120, 18.81823793545253 120, -121.71484375 120 M-121.71484375 120 C-121.71484375 48.101627292431374, -121.71484375 -23.796745415137252, -121.71484375 -120 M-121.71484375 120 C-121.71484375 37.34438462810408, -121.71484375 -45.31123074379184, -121.71484375 -120"/></g><g transform="translate(0, -96)" class="annotation-group text"/><g transform="translate(-34.2265625, -96)" class="label-group text"><g transform="translate(0,-10.5)" style="font-weight: bolder" class="label"><foreignObject height="21" width="68.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 117px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>Document</p></span></div></foreignObject></g></g><g transform="translate(-109.71484375, -51)" class="members-group text"><g transform="translate(0,-10.5)" style="" class="label"><foreignObject height="21" width="185.203125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 245px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>-IDocumentState currentState</p></span></div></foreignObject></g></g><g transform="translate(-109.71484375, -6)" class="methods-group text"><g transform="translate(0,-10.5)" style="" class="label"><foreignObject height="21" width="171.59375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 231px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+SetState(IDocumentState)</p></span></div></foreignObject></g><g transform="translate(0,10.5)" style="" class="label"><foreignObject height="21" width="75.859375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 137px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Edit(string)</p></span></div></foreignObject></g><g transform="translate(0,31.5)" style="" class="label"><foreignObject height="21" width="61.078125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 120px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Submit()</p></span></div></foreignObject></g><g transform="translate(0,52.5)" style="" class="label"><foreignObject height="21" width="69.65625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 130px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Approve()</p></span></div></foreignObject></g><g transform="translate(0,73.5)" style="" class="label"><foreignObject height="21" width="63.421875"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 122px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Publish()</p></span></div></foreignObject></g><g transform="translate(0,94.5)" style="" class="label"><foreignObject height="21" width="57.1875"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 115px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Reject()</p></span></div></foreignObject></g></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-121.71484375 -75 C-39.72915920594919 -74.99966320589166, 42.256525338101625 -74.99932641178333, 121.71484375 -74.999 M-121.71484375 -75 C-43.9134652637601 -74.99968039486356, 33.887913222479796 -74.99936078972712, 121.71484375 -74.999"/></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-121.71484375 -30 C-44.05920145985171 -29.999680993542373, 33.59644083029659 -29.99936198708475, 121.71484375 -29.999 M-121.71484375 -30 C-50.67564028879565 -29.999708173624217, 20.363563172408703 -29.99941634724843, 121.71484375 -29.999"/></g></g><g transform="translate(502.73828125, 431.5)" data-look="neo" data-et="node" data-node="true" data-id="IDocumentState" id="classId-IDocumentState-1" class="node default"><g class="basic label-container outer-path"><path style="" fill="#ffffff" stroke-width="0" stroke="none" d="M-112.37109375 -112.5 L112.37109375 -112.5 L112.37109375 112.5 L-112.37109375 112.5"/><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-112.37109375 -112.5 C-66.74091257816603 -112.5, -21.110731406332064 -112.5, 112.37109375 -112.5 M-112.37109375 -112.5 C-27.14633199611046 -112.5, 58.07842975777908 -112.5, 112.37109375 -112.5 M112.37109375 -112.5 C112.37109375 -63.89156364899078, 112.37109375 -15.283127297981565, 112.37109375 112.5 M112.37109375 -112.5 C112.37109375 -56.156534570227855, 112.37109375 0.18693085954429023, 112.37109375 112.5 M112.37109375 112.5 C61.644819461190565 112.5, 10.91854517238113 112.5, -112.37109375 112.5 M112.37109375 112.5 C54.232043876875174 112.5, -3.9070059962496515 112.5, -112.37109375 112.5 M-112.37109375 112.5 C-112.37109375 38.34899970858467, -112.37109375 -35.80200058283066, -112.37109375 -112.5 M-112.37109375 112.5 C-112.37109375 26.332211611982686, -112.37109375 -59.83557677603463, -112.37109375 -112.5"/></g><g transform="translate(-34.640625, -88.5)" class="annotation-group text"><g transform="translate(0,-10.5)" style="" class="label"><foreignObject height="21" width="69.28125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 122px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>«interface»</p></span></div></foreignObject></g></g><g transform="translate(-53.2890625, -67.5)" class="label-group text"><g transform="translate(0,-10.5)" style="font-weight: bolder" class="label"><foreignObject height="21" width="106.578125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 154px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>IDocumentState</p></span></div></foreignObject></g></g><g transform="translate(-100.37109375, -22.5)" class="members-group text"/><g transform="translate(-100.37109375, 1.5)" class="methods-group text"><g transform="translate(0,-10.5)" style="" class="label"><foreignObject height="21" width="147.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 212px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Edit(Document, string)</p></span></div></foreignObject></g><g transform="translate(0,10.5)" style="" class="label"><foreignObject height="21" width="124.890625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 187px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Submit(Document)</p></span></div></foreignObject></g><g transform="translate(0,31.5)" style="" class="label"><foreignObject height="21" width="133.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 197px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Approve(Document)</p></span></div></foreignObject></g><g transform="translate(0,52.5)" style="" class="label"><foreignObject height="21" width="127.234375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 189px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Publish(Document)</p></span></div></foreignObject></g><g transform="translate(0,73.5)" style="" class="label"><foreignObject height="21" width="121"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 182px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Reject(Document)</p></span></div></foreignObject></g></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-112.37109375 -46.5 C-36.952995092938465 -46.499664423936174, 38.46510356412307 -46.49932884787235, 112.37109375 -46.499 M-112.37109375 -46.5 C-22.768479815899752 -46.49960130932723, 66.8341341182005 -46.499202618654465, 112.37109375 -46.499"/></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-112.37109375 -22.5 C-63.92709475714094 -22.499784446349253, -15.483095764281884 -22.499568892698502, 112.37109375 -22.499 M-112.37109375 -22.5 C-36.431685263810735 -22.499662104345735, 39.50772322237853 -22.49932420869147, 112.37109375 -22.499"/></g></g><g transform="translate(110.453125, 717)" data-look="neo" data-et="node" data-node="true" data-id="DraftState" id="classId-DraftState-2" class="node default"><g class="basic label-container outer-path"><path style="" fill="#ffffff" stroke-width="0" stroke="none" d="M-102.453125 -102 L102.453125 -102 L102.453125 102 L-102.453125 102"/><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-102.453125 -102 C-48.13978947624658 -102, 6.173546047506846 -102, 102.453125 -102 M-102.453125 -102 C-42.321476825408155 -102, 17.81017134918369 -102, 102.453125 -102 M102.453125 -102 C102.453125 -24.715111698093892, 102.453125 52.569776603812215, 102.453125 102 M102.453125 -102 C102.453125 -33.030279271409185, 102.453125 35.93944145718163, 102.453125 102 M102.453125 102 C51.59038756809288 102, 0.7276501361857584 102, -102.453125 102 M102.453125 102 C28.91954677643382 102, -44.61403144713236 102, -102.453125 102 M-102.453125 102 C-102.453125 44.47984083312107, -102.453125 -13.040318333757867, -102.453125 -102 M-102.453125 102 C-102.453125 53.27949057756074, -102.453125 4.5589811551214865, -102.453125 -102"/></g><g transform="translate(0, -78)" class="annotation-group text"/><g transform="translate(-33.453125, -78)" class="label-group text"><g transform="translate(0,-10.5)" style="font-weight: bolder" class="label"><foreignObject height="21" width="66.90625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 116px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>DraftState</p></span></div></foreignObject></g></g><g transform="translate(-90.453125, -33)" class="members-group text"/><g transform="translate(-90.453125, -9)" class="methods-group text"><g transform="translate(0,-10.5)" style="" class="label"><foreignObject height="21" width="147.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 212px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Edit(Document, string)</p></span></div></foreignObject></g><g transform="translate(0,10.5)" style="" class="label"><foreignObject height="21" width="124.890625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 187px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Submit(Document)</p></span></div></foreignObject></g><g transform="translate(0,31.5)" style="" class="label"><foreignObject height="21" width="133.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 197px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Approve(Document)</p></span></div></foreignObject></g><g transform="translate(0,52.5)" style="" class="label"><foreignObject height="21" width="127.234375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 189px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Publish(Document)</p></span></div></foreignObject></g><g transform="translate(0,73.5)" style="" class="label"><foreignObject height="21" width="121"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 182px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Reject(Document)</p></span></div></foreignObject></g></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-102.453125 -57 C-52.822722558236244 -56.999757789708994, -3.1923201164724873 -56.99951557941799, 102.453125 -56.999 M-102.453125 -57 C-41.11171082971773 -56.99970063668546, 20.22970334056454 -56.99940127337092, 102.453125 -56.999"/></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-102.453125 -33 C-52.17370001207099 -32.99975462229684, -1.894275024141976 -32.99950924459368, 102.453125 -32.999 M-102.453125 -33 C-33.80407318691758 -32.999664973363124, 34.844978626164846 -32.99932994672624, 102.453125 -32.999"/></g></g><g transform="translate(369.25390625, 717)" data-look="neo" data-et="node" data-node="true" data-id="ReviewState" id="classId-ReviewState-3" class="node default"><g class="basic label-container outer-path"><path style="" fill="#ffffff" stroke-width="0" stroke="none" d="M-106.34765625 -102 L106.34765625 -102 L106.34765625 102 L-106.34765625 102"/><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-106.34765625 -102 C-26.513983998711055 -102, 53.31968825257789 -102, 106.34765625 -102 M-106.34765625 -102 C-63.791830447633146 -102, -21.23600464526629 -102, 106.34765625 -102 M106.34765625 -102 C106.34765625 -50.96212845795354, 106.34765625 0.0757430840929203, 106.34765625 102 M106.34765625 -102 C106.34765625 -46.93755905489017, 106.34765625 8.124881890219655, 106.34765625 102 M106.34765625 102 C38.89540226396143 102, -28.55685172207714 102, -106.34765625 102 M106.34765625 102 C33.49587690462896 102, -39.355902440742085 102, -106.34765625 102 M-106.34765625 102 C-106.34765625 56.847877610298774, -106.34765625 11.695755220597547, -106.34765625 -102 M-106.34765625 102 C-106.34765625 59.379431025579706, -106.34765625 16.75886205115941, -106.34765625 -102"/></g><g transform="translate(0, -78)" class="annotation-group text"/><g transform="translate(-41.2421875, -78)" class="label-group text"><g transform="translate(0,-10.5)" style="font-weight: bolder" class="label"><foreignObject height="21" width="82.484375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 131px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>ReviewState</p></span></div></foreignObject></g></g><g transform="translate(-94.34765625, -33)" class="members-group text"/><g transform="translate(-94.34765625, -9)" class="methods-group text"><g transform="translate(0,-10.5)" style="" class="label"><foreignObject height="21" width="147.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 212px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Edit(Document, string)</p></span></div></foreignObject></g><g transform="translate(0,10.5)" style="" class="label"><foreignObject height="21" width="124.890625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 187px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Submit(Document)</p></span></div></foreignObject></g><g transform="translate(0,31.5)" style="" class="label"><foreignObject height="21" width="133.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 197px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Approve(Document)</p></span></div></foreignObject></g><g transform="translate(0,52.5)" style="" class="label"><foreignObject height="21" width="127.234375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 189px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Publish(Document)</p></span></div></foreignObject></g><g transform="translate(0,73.5)" style="" class="label"><foreignObject height="21" width="121"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 182px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Reject(Document)</p></span></div></foreignObject></g></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-106.34765625 -57 C-36.67348142035503 -56.99967242261237, 33.00069340928994 -56.99934484522474, 106.34765625 -56.999 M-106.34765625 -57 C-53.072981621457124 -56.999749525864004, 0.20169300708575122 -56.99949905172801, 106.34765625 -56.999"/></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-106.34765625 -33 C-41.05146644919773 -32.999693005976326, 24.244723351604534 -32.99938601195265, 106.34765625 -32.999 M-106.34765625 -33 C-52.9666542748931 -32.999749025959495, 0.41434770021379563 -32.999498051918984, 106.34765625 -32.999"/></g></g><g transform="translate(636.22265625, 717)" data-look="neo" data-et="node" data-node="true" data-id="ApprovedState" id="classId-ApprovedState-4" class="node default"><g class="basic label-container outer-path"><path style="" fill="#ffffff" stroke-width="0" stroke="none" d="M-110.62109375 -102 L110.62109375 -102 L110.62109375 102 L-110.62109375 102"/><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-110.62109375 -102 C-29.384750797498512 -102, 51.851592155002976 -102, 110.62109375 -102 M-110.62109375 -102 C-36.88111781266059 -102, 36.85885812467882 -102, 110.62109375 -102 M110.62109375 -102 C110.62109375 -57.14500638872338, 110.62109375 -12.290012777446762, 110.62109375 102 M110.62109375 -102 C110.62109375 -22.501084161843934, 110.62109375 56.99783167631213, 110.62109375 102 M110.62109375 102 C57.51178053406476 102, 4.402467318129524 102, -110.62109375 102 M110.62109375 102 C46.944938090423406 102, -16.731217569153188 102, -110.62109375 102 M-110.62109375 102 C-110.62109375 26.322981961298623, -110.62109375 -49.35403607740275, -110.62109375 -102 M-110.62109375 102 C-110.62109375 50.33777216200555, -110.62109375 -1.3244556759889008, -110.62109375 -102"/></g><g transform="translate(0, -78)" class="annotation-group text"/><g transform="translate(-49.7890625, -78)" class="label-group text"><g transform="translate(0,-10.5)" style="font-weight: bolder" class="label"><foreignObject height="21" width="99.578125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 146px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>ApprovedState</p></span></div></foreignObject></g></g><g transform="translate(-98.62109375, -33)" class="members-group text"/><g transform="translate(-98.62109375, -9)" class="methods-group text"><g transform="translate(0,-10.5)" style="" class="label"><foreignObject height="21" width="147.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 212px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Edit(Document, string)</p></span></div></foreignObject></g><g transform="translate(0,10.5)" style="" class="label"><foreignObject height="21" width="124.890625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 187px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Submit(Document)</p></span></div></foreignObject></g><g transform="translate(0,31.5)" style="" class="label"><foreignObject height="21" width="133.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 197px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Approve(Document)</p></span></div></foreignObject></g><g transform="translate(0,52.5)" style="" class="label"><foreignObject height="21" width="127.234375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 189px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Publish(Document)</p></span></div></foreignObject></g><g transform="translate(0,73.5)" style="" class="label"><foreignObject height="21" width="121"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 182px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Reject(Document)</p></span></div></foreignObject></g></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-110.62109375 -57 C-36.86205441569966 -56.999666614038816, 36.89698491860068 -56.99933322807763, 110.62109375 -56.999 M-110.62109375 -57 C-40.89482781564014 -56.99968484190686, 28.831438118719717 -56.999369683813725, 110.62109375 -56.999"/></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-110.62109375 -33 C-48.91785703378833 -32.99972110546631, 12.78537968242334 -32.999442210932614, 110.62109375 -32.999 M-110.62109375 -33 C-32.603300622198034 -32.999647364754395, 45.41449250560393 -32.99929472950879, 110.62109375 -32.999"/></g></g><g transform="translate(907.85546875, 717)" data-look="neo" data-et="node" data-node="true" data-id="PublishedState" id="classId-PublishedState-5" class="node default"><g class="basic label-container outer-path"><path style="" fill="#ffffff" stroke-width="0" stroke="none" d="M-111.01171875 -102 L111.01171875 -102 L111.01171875 102 L-111.01171875 102"/><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-111.01171875 -102 C-25.508273733481076 -102, 59.99517128303785 -102, 111.01171875 -102 M-111.01171875 -102 C-26.52240119765885 -102, 57.9669163546823 -102, 111.01171875 -102 M111.01171875 -102 C111.01171875 -42.99690440074255, 111.01171875 16.006191198514898, 111.01171875 102 M111.01171875 -102 C111.01171875 -58.07089064996823, 111.01171875 -14.141781299936454, 111.01171875 102 M111.01171875 102 C58.70004777113795 102, 6.388376792275906 102, -111.01171875 102 M111.01171875 102 C40.202196557531565 102, -30.60732563493687 102, -111.01171875 102 M-111.01171875 102 C-111.01171875 41.719415976706, -111.01171875 -18.561168046587994, -111.01171875 -102 M-111.01171875 102 C-111.01171875 23.566245594130507, -111.01171875 -54.867508811738986, -111.01171875 -102"/></g><g transform="translate(0, -78)" class="annotation-group text"/><g transform="translate(-50.5703125, -78)" class="label-group text"><g transform="translate(0,-10.5)" style="font-weight: bolder" class="label"><foreignObject height="21" width="101.140625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 145px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>PublishedState</p></span></div></foreignObject></g></g><g transform="translate(-99.01171875, -33)" class="members-group text"/><g transform="translate(-99.01171875, -9)" class="methods-group text"><g transform="translate(0,-10.5)" style="" class="label"><foreignObject height="21" width="147.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 212px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Edit(Document, string)</p></span></div></foreignObject></g><g transform="translate(0,10.5)" style="" class="label"><foreignObject height="21" width="124.890625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 187px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Submit(Document)</p></span></div></foreignObject></g><g transform="translate(0,31.5)" style="" class="label"><foreignObject height="21" width="133.453125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 197px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Approve(Document)</p></span></div></foreignObject></g><g transform="translate(0,52.5)" style="" class="label"><foreignObject height="21" width="127.234375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 189px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Publish(Document)</p></span></div></foreignObject></g><g transform="translate(0,73.5)" style="" class="label"><foreignObject height="21" width="121"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 182px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="" class="nodeLabel markdown-node-label"><p>+Reject(Document)</p></span></div></foreignObject></g></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-111.01171875 -57 C-66.04082286698832 -56.99979744978103, -21.06992698397663 -56.999594899562055, 111.01171875 -56.999 M-111.01171875 -57 C-36.90462724323851 -56.99966621951114, 37.202464263522984 -56.99933243902228, 111.01171875 -56.999"/></g><g style="" class="divider"><path style="" fill="none" stroke-width="1.3" stroke="#000000" d="M-111.01171875 -33 C-57.380020692826285 -32.99975844127692, -3.7483226356525705 -32.999516882553834, 111.01171875 -32.999 M-111.01171875 -33 C-53.07980031662474 -32.999739072959656, 4.852118116750518 -32.99947814591932, 111.01171875 -32.999"/></g></g></g></g></g><defs><filter width="130%" height="130%" id="drop-shadow"><feDropShadow flood-color="#FFFFFF" flood-opacity="0.06" stdDeviation="0" dy="4" dx="4"/></filter></defs><defs><filter width="150%" height="150%" id="drop-shadow-small"><feDropShadow flood-color="#FFFFFF" flood-opacity="0.06" stdDeviation="0" dy="2" dx="2"/></filter></defs><linearGradient y2="0%" x2="100%" y1="0%" x1="0%" gradientUnits="objectBoundingBox" id="my-svg-gradient"><stop stop-opacity="1" stop-color="#0042eb" offset="0%"/><stop stop-opacity="1" stop-color="#eb0042" offset="100%"/></linearGradient></svg>
<!--kg-card-end: html-->
<p>All <strong>states</strong> are inheriting from <code>IDocumentState</code> to make sure that all business logic is taken care of in the state implementation itself. This will promote the SOLID principles and make transitions explicit. What's not to like!? 🔥</p><p>Let's take a look at how a document can change states and the lifecycle of the document in our app.</p>
<!--kg-card-begin: html-->
<svg aria-roledescription="stateDiagram" role="graphics-document document" viewBox="0 0 186.0859375 445" style="max-width: 640px; background-color: transparrent; max-height: 640px;" class="statediagram" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="100%" id="my-svg"><style>#my-svg{font-family:arial,sans-serif;font-size:14px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#my-svg .error-icon{fill:#ffffff;}#my-svg .error-text{fill:#000000;stroke:#000000;}#my-svg .edge-thickness-normal{stroke-width:2px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:#000000;stroke:#000000;}#my-svg .marker.cross{stroke:#000000;}#my-svg svg{font-family:arial,sans-serif;font-size:14px;}#my-svg p{margin:0;}#my-svg defs #statediagram-barbEnd{fill:#000000;stroke:#000000;}#my-svg g.stateGroup text{fill:#000000;stroke:none;font-size:10px;}#my-svg g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#my-svg g.stateGroup .state-title{font-weight:bolder;fill:#333;}#my-svg g.stateGroup rect{fill:#ffffff;stroke:#000000;}#my-svg g.stateGroup line{stroke:#000000;stroke-width:1;}#my-svg .transition{stroke:#000000;stroke-width:2;fill:none;}#my-svg .stateGroup .composit{fill:#ffffff;border-bottom:1px;}#my-svg .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#my-svg .state-note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#my-svg .state-note text{fill:#333;stroke:none;font-size:10px;}#my-svg .stateLabel .box{stroke:none;stroke-width:0;fill:#ffffff;opacity:0.5;}#my-svg .edgeLabel .label rect{fill:#ffffff;opacity:0.5;}#my-svg .edgeLabel{background-color:hsl(-120, 0%, 80%);text-align:center;}#my-svg .edgeLabel p{background-color:hsl(-120, 0%, 80%);}#my-svg .edgeLabel rect{opacity:0.5;background-color:hsl(-120, 0%, 80%);fill:hsl(-120, 0%, 80%);}#my-svg .edgeLabel .label text{fill:#333;}#my-svg .label div .edgeLabel{color:#333;}#my-svg .stateLabel text{fill:#333;font-size:10px;font-weight:bold;}#my-svg .node circle.state-start{fill:#000000;stroke:#000000;}#my-svg .node .fork-join{fill:#000000;stroke:#000000;}#my-svg .node circle.state-end{fill:#ffffff;stroke:#ffffff;stroke-width:1.5;}#my-svg [data-look="neo"].node circle.state-end{filter:none;stroke:#ffffff;fill:#000000;}#my-svg .end-state-inner{fill:#ffffff;stroke:#ffffff;stroke-width:1.5;}#my-svg .node rect{fill:#ffffff;stroke:#000000;stroke-width:2;}#my-svg .node-rect-neo{fill:#ffffff;stroke:none;stroke-width:1px;}#my-svg .node polygon{fill:#ffffff;stroke:#000000;stroke-width:1px;}#my-svg #statediagram-barbEnd{fill:#000000;}#my-svg .statediagram-cluster rect{fill:#ffffff;stroke:#000000;stroke-width:2;}#my-svg .cluster-label,#my-svg .nodeLabel{color:#333;line-height:1.0;font-weight:undefined;}#my-svg .statediagram-cluster rect.outer{rx:3px;ry:3px;}#my-svg .statediagram-state .divider{stroke:#000000;}#my-svg .statediagram-state .title-state{rx:3px;ry:3px;}#my-svg .statediagram-cluster.statediagram-cluster .inner{fill:#ffffff;}#my-svg .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#my-svg .statediagram-cluster .inner{rx:0;ry:0;}#my-svg .statediagram-state rect.basic{rx:3px;ry:3px;}#my-svg .state-shadow-neo{filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#my-svg .note-edge{stroke-dasharray:5;}#my-svg .statediagram-note rect{fill:#fff5ad;stroke:hsl(52.6829268293, 60%, 73.9215686275%);stroke-width:1px;rx:0;ry:0;}#my-svg .statediagram-note rect{fill:#fff5ad;stroke:hsl(52.6829268293, 60%, 73.9215686275%);stroke-width:1px;rx:0;ry:0;}#my-svg .statediagram-note text{fill:#333;}#my-svg .statediagram-note .nodeLabel{color:#333;}#my-svg .node.statediagram-note rect{stroke:hsl(52.6829268293, 60%, 73.9215686275%)!important;}#my-svg .statediagram .edgeLabel{color:red;}#my-svg #dependencyStart,#my-svg #dependencyEnd{fill:#000000;stroke:#000000;stroke-width:1;}#my-svg .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#my-svg [data-look="neo"].statediagram-cluster rect.outer{rx:3px;ry:3px;filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg [data-look="neo"].statediagram-cluster .inner{rx:3px;ry:3px;}#my-svg .node .neo-node{stroke:#000000;}#my-svg [data-look="neo"].node rect,#my-svg [data-look="neo"].cluster rect,#my-svg [data-look="neo"].node polygon{stroke:url(#my-svg-gradient);filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg [data-look="neo"].node path{stroke:url(#my-svg-gradient);stroke-width:2;}#my-svg [data-look="neo"].node .outer-path{filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg [data-look="neo"].node .neo-line path{stroke:#000000;filter:none;}#my-svg [data-look="neo"].node circle{stroke:url(#my-svg-gradient);filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg [data-look="neo"].node circle .state-start{fill:#000000;}#my-svg [data-look="neo"].statediagram-cluster rect{fill:#ffffff;stroke:url(#my-svg-gradient);stroke-width:2;}#my-svg [data-look="neo"].icon-shape .icon{fill:url(#my-svg-gradient);filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg [data-look="neo"].icon-shape .icon-neo path{stroke:url(#my-svg-gradient);filter:drop-shadow( 0px 1px 2px rgba(0, 0, 0, 0.25));}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><defs><marker orient="auto" markerUnits="strokeWidth" markerHeight="14" markerWidth="20" refY="7" refX="19" id="my-svg_stateDiagram-barbEnd"><path d="M 19,7 L11,14 L13,7 L11,0 Z"/></marker></defs><defs><marker orient="auto" markerUnits="userSpaceOnUse" markerHeight="14" markerWidth="20" refY="7" refX="17" id="my-svg_stateDiagram-barbEnd-margin"><path fill="#000000" d="M 19,7 L11,14 L13,7 L11,0 Z"/></marker></defs><g class="root"><g class="clusters"/><g class="edgePaths"><path marker-end="url(#my-svg_stateDiagram-barbEnd-margin)" data-points="W3sieCI6OTEuNDIxODc1LCJ5IjoyMn0seyJ4Ijo5MS40MjE4NzUsInkiOjQ3fSx7IngiOjkxLjQyMTg3NSwieSI6NzJ9XQ==" data-id="edge0" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 44.5 0; stroke-dashoffset: 0;fill:none;;;fill:none" class="edge-thickness-normal edge-pattern-solid transition" id="edge0" d="M91.421875,22L91.421875,47L91.421875,66.5"/><path marker-end="url(#my-svg_stateDiagram-barbEnd-margin)" data-points="W3sieCI6NjkuOTM1MjA2NDIyMDE4MzUsInkiOjExMH0seyJ4IjoyOS43ODkwNjI1LCJ5IjoxNDUuNX0seyJ4Ijo0OS44NjIxMzQ0NjEwMDkxOCwieSI6MTgxfV0=" data-id="edge1" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 86.57231903076172 0; stroke-dashoffset: 0;fill:none;;;fill:none" class="edge-thickness-normal edge-pattern-solid transition" id="edge1" d="M69.93520642201835,110L35.74091143209651,140.23696309414308Q29.7890625,145.5 33.69964921154071,152.41602304467182L47.15501677151842,176.21235821982825"/><path marker-end="url(#my-svg_stateDiagram-barbEnd-margin)" data-points="W3sieCI6NjAuNjA1NDY4NzUsInkiOjIxOX0seyJ4Ijo2MC42MDU0Njg3NSwieSI6MjU0LjV9LHsieCI6OTEuMTUzOTkyMjU5MTc0MzIsInkiOjI5MH1d" data-id="edge2" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 75.64530181884766 0; stroke-dashoffset: 0;fill:none;;;fill:none" class="edge-thickness-normal edge-pattern-solid transition" id="edge2" d="M60.60546875,219L60.60546875,240.12631844883876Q60.60546875,254.5 69.98094109810171,265.3951016325766L87.56652597706558,285.8310570074978"/><path marker-end="url(#my-svg_stateDiagram-barbEnd-margin)" data-points="W3sieCI6NzEuMzQ4ODAzMDM4OTkwODIsInkiOjE4MX0seyJ4Ijo5MS40MjE4NzUsInkiOjE0NS41fSx7IngiOjkxLjQyMTg3NSwieSI6MTEwfV0=" data-id="edge3" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 70.12588500976562 0; stroke-dashoffset: 0;fill:none;;;fill:none" class="edge-thickness-normal edge-pattern-solid transition" id="edge3" d="M71.34880303899082,181L84.0388267559343,158.5572048550139Q91.421875,145.5 91.421875,130.5L91.421875,115.5"/><path marker-end="url(#my-svg_stateDiagram-barbEnd-margin)" data-points="W3sieCI6MTA3LjUwMzkwNjI1LCJ5IjozMjh9LHsieCI6MTA3LjUwMzkwNjI1LCJ5IjozNjMuNX0seyJ4IjoxMDcuNTAzOTA2MjUsInkiOjM5OX1d" data-id="edge4" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 65.5 0; stroke-dashoffset: 0;fill:none;;;fill:none" class="edge-thickness-normal edge-pattern-solid transition" id="edge4" d="M107.50390625,328L107.50390625,363.5L107.50390625,393.5"/><path marker-end="url(#my-svg_stateDiagram-barbEnd-margin)" data-points="W3sieCI6MTI1LjE5MjQ4MTM2NDY3ODksInkiOjI5MH0seyJ4IjoxNTguMjQyMTg3NSwieSI6MjU0LjV9LHsieCI6MTU4LjI0MjE4NzUsInkiOjIwMH0seyJ4IjoxNTguMjQyMTg3NSwieSI6MTQ1LjV9LHsieCI6MTE0LjcxNzAyOTgxNjUxMzc2LCJ5IjoxMTB9XQ==" data-id="edge5" data-et="edge" data-edge="true" style="stroke-dasharray: 0 0 205.42752075195312 0; stroke-dashoffset: 0;fill:none;;;fill:none" class="edge-thickness-normal edge-pattern-solid transition" id="edge5" d="M125.1924813646789,290L148.93650449446537,264.4956031482962Q158.2421875,254.5 158.2421875,240.84321346371246L158.2421875,200L158.2421875,157.15707018059882Q158.2421875,145.5 149.2087828595903,138.13217224698957L118.97914068592787,113.47626393371131"/></g><g class="edgeLabels"><g class="edgeLabel"><g transform="translate(0, 0)" data-id="edge0" class="label"><foreignObject height="0" width="0"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" class="labelBkg" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g transform="translate(29.7890625, 145.5)" class="edgeLabel"><g transform="translate(-21.7890625, -10.5)" data-id="edge1" class="label"><foreignObject height="21" width="43.578125"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" class="labelBkg" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"><p>Submit</p></span></div></foreignObject></g></g><g transform="translate(60.60546875, 254.5)" class="edgeLabel"><g transform="translate(-26.078125, -10.5)" data-id="edge2" class="label"><foreignObject height="21" width="52.15625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" class="labelBkg" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"><p>Approve</p></span></div></foreignObject></g></g><g transform="translate(91.421875, 145.5)" class="edgeLabel"><g transform="translate(-19.84375, -10.5)" data-id="edge3" class="label"><foreignObject height="21" width="39.6875"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" class="labelBkg" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"><p>Reject</p></span></div></foreignObject></g></g><g transform="translate(107.50390625, 363.5)" class="edgeLabel"><g transform="translate(-22.9609375, -10.5)" data-id="edge4" class="label"><foreignObject height="21" width="45.921875"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" class="labelBkg" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"><p>Publish</p></span></div></foreignObject></g></g><g transform="translate(158.2421875, 200)" class="edgeLabel"><g transform="translate(-19.84375, -10.5)" data-id="edge5" class="label"><foreignObject height="21" width="39.6875"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" class="labelBkg" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"><p>Reject</p></span></div></foreignObject></g></g></g><g class="nodes"><g transform="translate(91.421875, 15)" data-look="neo" data-et="node" data-node="true" data-id="root_start" id="state-root_start-0" class="node default"><circle height="14" width="14" r="7" class="state-start"/></g><g transform="translate(91.421875, 91)" data-look="neo" data-et="node" data-node="true" data-id="Draft" id="state-Draft-5" class="node  statediagram-state"><rect stroke="url(#gradient)" height="38" width="62.34375" y="-19" x="-31.171875" ry="3" data-id="Draft" rx="3" style="" class="basic label-container"/><g transform="translate(-15.171875, -7)" style="" class="label"><rect/><foreignObject height="14" width="30.34375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"><p>Draft</p></span></div></foreignObject></g></g><g transform="translate(60.60546875, 200)" data-look="neo" data-et="node" data-node="true" data-id="Review" id="state-Review-3" class="node  statediagram-state"><rect stroke="url(#gradient)" height="38" width="77.90625" y="-19" x="-38.953125" ry="3" data-id="Review" rx="3" style="" class="basic label-container"/><g transform="translate(-22.953125, -7)" style="" class="label"><rect/><foreignObject height="14" width="45.90625"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"><p>Review</p></span></div></foreignObject></g></g><g transform="translate(107.50390625, 309)" data-look="neo" data-et="node" data-node="true" data-id="Approved" id="state-Approved-5" class="node  statediagram-state"><rect stroke="url(#gradient)" height="38" width="91.9375" y="-19" x="-45.96875" ry="3" data-id="Approved" rx="3" style="" class="basic label-container"/><g transform="translate(-29.96875, -7)" style="" class="label"><rect/><foreignObject height="14" width="59.9375"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"><p>Approved</p></span></div></foreignObject></g></g><g transform="translate(107.50390625, 418)" data-look="neo" data-et="node" data-node="true" data-id="Published" id="state-Published-4" class="node  statediagram-state"><rect stroke="url(#gradient)" height="38" width="93.5" y="-19" x="-46.75" ry="3" data-id="Published" rx="3" style="" class="basic label-container"/><g transform="translate(-30.75, -7)" style="" class="label"><rect/><foreignObject height="14" width="61.5"><div style="display: table-cell; white-space: normal; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"><p>Published</p></span></div></foreignObject></g></g></g></g></g><defs><filter width="130%" height="130%" id="drop-shadow"><feDropShadow flood-color="#FFFFFF" flood-opacity="0.06" stdDeviation="0" dy="4" dx="4"/></filter></defs><defs><filter width="150%" height="150%" id="drop-shadow-small"><feDropShadow flood-color="#FFFFFF" flood-opacity="0.06" stdDeviation="0" dy="2" dx="2"/></filter></defs><linearGradient y2="0%" x2="100%" y1="0%" x1="0%" gradientUnits="objectBoundingBox" id="my-svg-gradient"><stop stop-opacity="1" stop-color="#0042eb" offset="0%"/><stop stop-opacity="1" stop-color="#eb0042" offset="100%"/></linearGradient></svg>
<!--kg-card-end: html-->
<p>In all its essence, it is fairly simple. A document will always be created in a <strong>draft state</strong> (<em>the context class will handle that when constructing the document</em>).</p><h2 id="%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB-implementing-the-state-pattern">🧑‍💻 Implementing the State Pattern</h2><p>So, let's move on to the good stuff and write some code that will solve our problem. I will start by defining the <code>IDocumentState</code> that will define the states for a Document.</p><pre><code class="language-csharp">public interface IDocumentState
{
    void Edit(Document document, string content);
    void Submit(Document document);
    void Approve(Document document);
    void Publish(Document document);
    void Reject(Document document);
    string GetStateName();
}</code></pre><p>Let's implement the concrete classes for each state. As mentioned earlier, they will inherit from the abstraction `IDocumentState`.</p><pre><code class="language-csharp">public class DraftState : IDocumentState
{
    public void Edit(Document document, string content)
    {
        document.Content = content;
    }
    
    public void Submit(Document document)
    {
        document.SetState(new ReviewState());
    }
    
    public void Approve(Document document)
    {
        throw new InvalidOperationException("Cannot approve a draft document. It must be submitted for review first.");
    }
    
    public void Publish(Document document)
    {
        throw new InvalidOperationException("Cannot publish a draft document. It must be reviewed and approved first.");
    }
    
    public void Reject(Document document)
    {
        throw new InvalidOperationException("Cannot reject a draft document. It must be submitted for review first.");
    }
    
    public string GetStateName() =&gt; "Draft";
}

public class ReviewState : IDocumentState
{
    public void Edit(Document document, string content)
    {
        throw new InvalidOperationException("Cannot edit a document that is under review.");
    }
    
    public void Submit(Document document)
    {
        throw new InvalidOperationException("Document is already under review.");
    }
    
    public void Approve(Document document)
    {
        document.SetState(new ApprovedState());
    }
    
    public void Publish(Document document)
    {
        throw new InvalidOperationException("Cannot publish a document under review. It must be approved first.");
    }
    
    public void Reject(Document document)
    {
        document.SetState(new DraftState());
    }
    
    public string GetStateName() =&gt; "In Review";
}

public class ApprovedState : IDocumentState
{
    public void Edit(Document document, string content)
    {
        throw new InvalidOperationException("Cannot edit an approved document.");
    }
    
    public void Submit(Document document)
    {
        throw new InvalidOperationException("Document is already approved.");
    }
    
    public void Approve(Document document)
    {
        throw new InvalidOperationException("Document is already approved.");
    }
    
    public void Publish(Document document)
    {
        document.SetState(new PublishedState());
    }
    
    public void Reject(Document document)
    {
        document.SetState(new DraftState());
    }
    
    public string GetStateName() =&gt; "Approved";
}

public class PublishedState : IDocumentState
{
    public void Edit(Document document, string content)
    {
        throw new InvalidOperationException("Cannot edit a published document.");
    }
    
    public void Submit(Document document)
    {
        throw new InvalidOperationException("Document is already published.");
    }
    
    public void Approve(Document document)
    {
        throw new InvalidOperationException("Document is already published.");
    }
    
    public void Publish(Document document)
    {
        throw new InvalidOperationException("Document is already published.");
    }
    
    public void Reject(Document document)
    {
        throw new InvalidOperationException("Cannot reject a published document.");
    }
    
    public string GetStateName() =&gt; "Published";
}</code></pre><p>Finally we will implement the context class for the <code>Document</code> where the state of the document can be changed.</p><pre><code class="language-csharp">public class Document
{
    private IDocumentState _currentState;
    
    public string Content { get; set; }
    
    public Document()
    {
        // Initial state is Draft
        _currentState = new DraftState();
    }
    
    public void SetState(IDocumentState state)
    {
        _currentState = state;
    }
    
    public string GetCurrentStateName() =&gt; _currentState.GetStateName();
    
    // Operations delegated to the current state
    public void Edit(string content) =&gt; _currentState.Edit(this, content);
    
    public void Submit() =&gt; _currentState.Submit(this);
    
    public void Approve() =&gt; _currentState.Approve(this);
    
    public void Publish() =&gt; _currentState.Publish(this);
    
    public void Reject() =&gt; _currentState.Reject(this);
}</code></pre><p>As you can see a <code>Document</code> is now capable of changing <strong>state</strong> to any of the defined states, as they have been defined using the abstraction <code>IDocumentState</code> for the state implementation.</p><p>The constructor of the <code>Document</code> class makes sure that the initial state of the document is set to <code>DraftState</code>. When you try to change the state of the document, each state will make sure to validate if you can actually change the state to that state your are trying to. It's readable and maintainable! No more <code>if-else</code> statements! 🙌</p><h2 id="%F0%9F%92%9B-key-take-aways-from-the-changed-implementation">💛 Key take aways from the changed implementation</h2><p>By refactoring the implementation from the conditional logic to the state pattern, we have achieved several benefits.</p><ul><li>We have made the <code>Document</code> state transitions explicit. This means transitions are now clear and contained within the Document itself.</li><li>All conditional logic has been removed, as each state is responsible of its own behavior. By doing this we conform with the <strong>single responsibility principle</strong>.</li><li>Adding new states is made easy, simply by adding a new state class and inheriting from the abstraction. By doing this we comply with the <strong>open/closed principle</strong>, as new states doesn't require us to change existing code.</li><li>Testing is now made much easier as we can test each state implementation isolated.</li></ul><p>Now that you have seen how to implement the business logic, let's take a look at how we can unit test it properly.</p><h2 id="%E2%9C%85-unit-testing-the-implementation">✅ Unit Testing the implementation</h2><p>Alright, we have the business logic in place but what about testing. Let's see how we can properly unit test our business logic for the states using <a href="https://xunit.net/?ref=christian-schou.com" rel="noreferrer">xUnit</a>, <a href="https://nsubstitute.github.io/?ref=christian-schou.com" rel="noreferrer">NSubstitute</a>, and <a href="https://awesomeassertions.org/?ref=christian-schou.com" rel="noreferrer">AwesomeAssertions</a> for adding some syntactic sugar.</p><pre><code class="language-csharp">using System;
using FluentAssertions;
using NSubstitute;
using Xunit;

public class DocumentStateTests
{
    [Fact]
    public void Document_ShouldStartInDraftState()
    {
        // Arrange
        var document = new Document();
        
        // Act &amp; Assert
        document.GetCurrentStateName().Should().Be("Draft");
    }
    
    [Fact]
    public void DraftDocument_CanBeEdited()
    {
        // Arrange
        var document = new Document();
        var newContent = "Updated content";
        
        // Act
        document.Edit(newContent);
        
        // Assert
        document.Content.Should().Be(newContent);
    }
    
    [Fact]
    public void DraftDocument_CanBeSubmittedForReview()
    {
        // Arrange
        var document = new Document();
        
        // Act
        document.Submit();
        
        // Assert
        document.GetCurrentStateName().Should().Be("In Review");
    }
    
    [Fact]
    public void DocumentInReview_CannotBeEdited()
    {
        // Arrange
        var document = new Document();
        document.Submit(); // Move to review state
        
        // Act &amp; Assert
        Action editAction = () =&gt; document.Edit("New content");
        editAction.Should().Throw&lt;InvalidOperationException&gt;()
            .WithMessage("Cannot edit a document that is under review.");
    }
    
    [Fact]
    public void DocumentInReview_CanBeApproved()
    {
        // Arrange
        var document = new Document();
        document.Submit(); // Move to review state
        
        // Act
        document.Approve();
        
        // Assert
        document.GetCurrentStateName().Should().Be("Approved");
    }
    
    [Fact]
    public void DocumentInReview_CanBeRejected()
    {
        // Arrange
        var document = new Document();
        document.Submit(); // Move to review state
        
        // Act
        document.Reject();
        
        // Assert
        document.GetCurrentStateName().Should().Be("Draft");
    }
    
    [Fact]
    public void ApprovedDocument_CanBePublished()
    {
        // Arrange
        var document = new Document();
        document.Submit(); // Move to review state
        document.Approve(); // Move to approved state
        
        // Act
        document.Publish();
        
        // Assert
        document.GetCurrentStateName().Should().Be("Published");
    }
    
    [Fact]
    public void PublishedDocument_CannotBeEdited()
    {
        // Arrange
        var document = new Document();
        document.Submit(); // Move to review state
        document.Approve(); // Move to approved state
        document.Publish(); // Move to published state
        
        // Act &amp; Assert
        Action editAction = () =&gt; document.Edit("New content");
        editAction.Should().Throw&lt;InvalidOperationException&gt;()
            .WithMessage("Cannot edit a published document.");
    }
}</code></pre><p>That is how you can test the transition workflow for a document. But what about a real-world project? Are you interested in seeing how this could be applied in e.g. a web API?</p><h2 id="%E2%9C%A8-a-real-world-exampledocument-workflow-api">✨ A Real-World example - Document Workflow API</h2><p>We have the business logic in place for how a document should be able to change state. Let's try implementing this in an actual application. For demonstration purposes, I will be creating a Document Workflow API which allows users to manage documents in a document repository.</p><p>Here is the code seen from a high-level to give you an idea on how I am designing the controller using the document repository.</p><pre><code class="language-csharp">// DocumentController.cs
[ApiController]
[Route("api/[controller]")]
public class DocumentController : ControllerBase
{
    private readonly IDocumentRepository _documentRepository;
    
    public DocumentController(IDocumentRepository documentRepository)
    {
        _documentRepository = documentRepository;
    }
    
    [HttpPut("{id}/content")]
    public IActionResult UpdateContent(int id, [FromBody] UpdateContentRequest request)
    {
        try
        {
            var document = _documentRepository.GetById(id);
            document.Edit(request.Content);
            _documentRepository.Save(document);
            return Ok();
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(ex.Message);
        }
    }
    
    [HttpPost("{id}/submit")]
    public IActionResult Submit(int id)
    {
        try
        {
            var document = _documentRepository.GetById(id);
            document.Submit();
            _documentRepository.Save(document);
            return Ok();
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(ex.Message);
        }
    }
    
    [HttpPost("{id}/approve")]
    public IActionResult Approve(int id)
    {
        try
        {
            var document = _documentRepository.GetById(id);
            document.Approve();
            _documentRepository.Save(document);
            return Ok();
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(ex.Message);
        }
    }
    
    [HttpPost("{id}/publish")]
    public IActionResult Publish(int id)
    {
        try
        {
            var document = _documentRepository.GetById(id);
            document.Publish();
            _documentRepository.Save(document);
            return Ok();
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(ex.Message);
        }
    }
    
    [HttpPost("{id}/reject")]
    public IActionResult Reject(int id)
    {
        try
        {
            var document = _documentRepository.GetById(id);
            document.Reject();
            _documentRepository.Save(document);
            return Ok();
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(ex.Message);
        }
    }
}

public class UpdateContentRequest
{
    public string Content { get; set; }
}

// IDocumentRepository.cs
public interface IDocumentRepository
{
    Document GetById(int id);
    void Save(Document document);
}</code></pre><p>This is a simple API that uses the state of each document to handle requests. I know business logic should not live in the controller actions, but for demo purposes it should be fine. 😅</p><h2 id="%F0%9F%99%8C-improving-the-pattern-with-dependency-injection">🙌 Improving the pattern with dependency injection</h2><p>You might have given it a thought, but how can we act in the state implementations when something happens and we want to trigger something outside the state class? I am so glad you asked 😆</p><p>In a real world scenario, we probably want to use dependency injection in our states to apply more complex logic. For demo purpose, I will show you an example with the <code>ReviewState</code>. We are going to refactor the <code>Approve</code> state a little by making it validating the document and ecent send a notification on success.</p><pre><code class="language-csharp">public class ReviewState : IDocumentState
{
    private readonly INotificationService _notificationService;
    private readonly IDocumentValidator _validator;
    
    public ReviewState(INotificationService notificationService, IDocumentValidator validator)
    {
        _notificationService = notificationService;
        _validator = validator;
    }
    
    public async Task Edit(Document document, string content)
    {
        throw new InvalidOperationException("Cannot edit a document that is under review.");
    }
    
    public async Task Submit(Document document)
    {
        throw new InvalidOperationException("Document is already under review.");
    }
    
    public async Task Approve(Document document)
    {
        // Check if the document meets requirements
        var validationResult = await _validator.ValidateForApproval(document);
        if (!validationResult.IsValid)
        {
            throw new ValidationException(validationResult.ErrorMessage);
        }
        
        var approvedState = new ApprovedState(_notificationService);
        document.SetState(approvedState);
        
        // Notify relevant parties
        await _notificationService.NotifyDocumentApproved(document);
    }
    
    // You can add more methods if you need that...
    
    public string GetStateName() =&gt; "In Review";
}</code></pre><p>Get the point? 😊 Now we are performing validation using <code>IDocumentValidator</code> where you can have different business logic to make sure that a document is OK to be approved. When the document is approved a notification is sent or published in the app using the <code>INotificationService</code>.</p><h2 id="%F0%9F%97%82%EF%B8%8F-persisting-state-with-entity-framework-core">🗂️ Persisting State with Entity Framework Core</h2><p>So... one thing that will be a challenge for you is persisting the state of a document in a database. I am almost always using EF Core to persist my data, as code is generated from the model itself.</p><p>Let's start by defining a <code>DocumentEntity</code> that we can use to represent documents in the database.</p><pre><code class="language-csharp">public class DocumentEntity
{
    public int Id { get; set; }
    public string Content { get; set; }
    public string StateName { get; set; } // Store state name as string
}
</code></pre><p>Now let's add a simple implementation of the <code>IDocumentRepository</code> that we used in our <code>DocumentController</code> earlier. For this we need the <strong>database context</strong> and <strong>a factory</strong> to create state objects.</p><p>We can start by writing the factory. I will name it <code>IDocumentStateFactory</code> and add a method named <code>CreateState</code> which is responsible for creating the state using the state name.</p><pre><code class="language-csharp">public interface IDocumentStateFactory
{
    IDocumentState CreateState(string stateName);
}

public class DocumentStateFactory : IDocumentStateFactory
{
    private readonly IServiceProvider _serviceProvider;
    
    public DocumentStateFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    
    public IDocumentState CreateState(string stateName)
    {
        return stateName switch
        {
            "Draft" =&gt; _serviceProvider.GetRequiredService&lt;DraftState&gt;(),
            "In Review" =&gt; _serviceProvider.GetRequiredService&lt;ReviewState&gt;(),
            "Approved" =&gt; _serviceProvider.GetRequiredService&lt;ApprovedState&gt;(),
            "Published" =&gt; _serviceProvider.GetRequiredService&lt;PublishedState&gt;(),
            _ =&gt; throw new ArgumentException($"Unknown state: {stateName}")
        };
    }
}</code></pre><p>In the <code>DocumentRepository</code> we can now use this factory. It will just implement the two declared methods from the interface.</p><pre><code class="language-csharp">public class DocumentRepository : IDocumentRepository
{
    private readonly DocumentDbContext _dbContext;
    private readonly IDocumentStateFactory _stateFactory;
    
    public DocumentRepository(DocumentDbContext dbContext, IDocumentStateFactory stateFactory)
    {
        _dbContext = dbContext;
        _stateFactory = stateFactory;
    }
    
    public Document GetById(int id)
    {
        var entity = _dbContext.Documents.Find(id);
        if (entity == null)
        {
            throw new DocumentNotFoundException(id);
        }
        
        var document = new Document();
        document.Id = entity.Id;
        document.Content = entity.Content;
        
        // Recreate the state object based on the stored state name
        var state = _stateFactory.CreateState(entity.StateName);
        document.SetState(state);
        
        return document;
    }
    
    public void Save(Document document)
    {
        var entity = _dbContext.Documents.Find(document.Id) ?? new DocumentEntity { Id = document.Id };
        
        entity.Content = document.Content;
        entity.StateName = document.GetCurrentStateName();
        
        if (entity.Id == 0)
        {
            _dbContext.Documents.Add(entity);
        }
        
        _dbContext.SaveChanges();
    }
}</code></pre><p>Finally we have to register our services to the service container so the app knows how to resolve the services for dependency injection. I have made an extension class for <code>IServiceCollection</code> to handle that.</p><pre><code class="language-csharp">public void AddDocumentStates(this IServiceCollection services)
{
    services.AddScoped&lt;DraftState&gt;();
    services.AddScoped&lt;ReviewState&gt;();
    services.AddScoped&lt;ApprovedState&gt;();
    services.AddScoped&lt;PublishedState&gt;();
    services.AddScoped&lt;IDocumentStateFactory, DocumentStateFactory&gt;();
    services.AddScoped&lt;IDocumentRepository, DocumentRepository&gt;();
    // Other services...
}</code></pre><p>Add the migrations, and woila you have solution that is able to persist the states in the database as well.</p><h2 id="%F0%9F%92%AD-when-should-you-use-the-state-pattern-and-when-should-you-avoid-it">💭 When should you use the state pattern, and when should you avoid it?</h2><p>This is the hard part - when should you implement the state pattern, and when should you avoid it to keep the code base as small as possible? 😅</p><h3 id="%E2%9C%85-when-to-use-it">✅ When to use it?</h3><p>If you are looking for a way to avoid duplicating state-checks, you can utilize the state pattern to centralize your check behavior. This is great in scenarios where you have multiple behaviors associated with a single object, as we had in the example above.</p><p>If the object behavior depends on the current state of the object and you need to update the state during runtime, it would be a good idea to use the state pattern. This way you can avoid writing tens of if statements making the code complex to maintain.</p><p>When your application might change in the future in relation to the states and you would like to make sure that nothing currently implemented will break during addition of new states.</p><h3 id="%E2%9D%8C-when-to-avoid-it">❌ When to avoid it?</h3><p>If the object is not going to change state often or you already know that the business logic behind the object will remain the same from the first deployment, skip it.</p><p>When your objects only contains a few states and their behavior is very basic, implementing the full pattern might be a little overkill. This could be the case if you are just throing an exception like I did above for most of the state change operations.</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">💡</div><div class="kg-callout-text">You should always ask yourself this question: <b><strong style="white-space: pre-wrap;">Do I have complex transitions in object making the state change?</strong></b> If <b><strong style="white-space: pre-wrap;">yes</strong></b>, apply the pattern as it makes your life easier (<i><em class="italic" style="white-space: pre-wrap;">speaking of experience</em></i> 🥵).</div></div><h2 id="%F0%9F%93%9D-summary">📝 Summary</h2><p>In this .NET tutorial I have shown you how powerful a tool the state pattern is to manage complex state-dependent behaviors. By encapsulating our state-specific business logic into separate state classes, we can achieve the following:</p><ul><li>Zero to none sprawling conditional logic in our code. The tens of hundreds if-else statements are forever gone when it comes to the state change.</li><li>The codebase is made maintainable and future proof as we can easily extend it and it is testable at the same time!</li><li>State transitions are made explicit and controlled. The code is easier to read for you and for others who will refactor or extend it in the future.</li></ul><p>One thing I will tell you about design patterns is that they are awesome tools we have in our toolbox, but use them where it makes sense. Dont force them into your app because a senior developer or architect told you to in a task-description if it doesn't make sense.</p><p>If you got any questions, please let me know in the comments below. Until next time - happy coding! ✌️</p><h2 id="%F0%9F%8C%90-references">🌐 References</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://nsubstitute.github.io/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">A friendly substitute for .NET mocking libraries | NSubstitute</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-14.ico" alt=""><span class="kg-bookmark-author">NSubstitute</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/nsubstitute-32x32.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://xunit.net/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Home</div><div class="kg-bookmark-description">Documentation site for the xUnit.net unit testing framework</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/logo-128-transparent.png" alt=""><span class="kg-bookmark-author">xUnit.net</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/full-logo.svg" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://awesomeassertions.org/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Awesome Assertions</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-13.ico" alt=""><span class="kg-bookmark-author">Awesome Assertions</span><span class="kg-bookmark-publisher">The AwesomeAssertions team, Dennis Doomen</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/awesomeassertions-banner-masthead.svg" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[How To Use GitHub Actions To Build And Test .NET Projects]]></title>
                    <description><![CDATA[This short GitHub Actions tutorial will teach you how to use GitHub Actions with .NET. By the end of this tutorial, you will know an easy way of creating a new build template and publish it to your Git repository for your .NET project.]]></description>
                    <link>https://christian-schou.com/blog/how-to-use-github-actions-to-build-and-test-dotnet/</link>
                    <guid isPermaLink="false">677f976088f8f30001d55967</guid>

                        <category><![CDATA[DevOps]]></category>
                        <category><![CDATA[GitHub]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Thu, 09 Jan 2025 18:00:32 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2025/01/using-github-actions-to-build-and-test-dotnet.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2025/01/using-github-actions-to-build-and-test-dotnet.webp" alt="How To Use GitHub Actions To Build And Test .NET Projects"/> <p><a href="https://github.com/?ref=christian-schou.com" rel="noreferrer">GitHub</a> is primarily known for its awesome Git repository hosting features. Over the past year GitHub has expanded its capabilities to also include other <a href="https://github.com/features?ref=christian-schou.com" rel="noreferrer">features</a>. One of the services has been named <a href="https://github.com/features/actions?ref=christian-schou.com" rel="noreferrer">GitHub Actions</a>, and it's pretty cool if you ask me! ✌️</p><p>GitHub Actions was made available to the public in 2018 and has since gained in popularity due to its great extensibility and features. I could talk a lot about this tool, but that's not what you are here for. 😅</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/features/actions?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub Actions</div><div class="kg-bookmark-description">Easily build, package, release, update, and deploy your project in any language—on GitHub or any external system—without having to run code yourself.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/pinned-octocat-093da3e6fa40-4.svg" alt=""><span class="kg-bookmark-author">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/actions-de2bd3e42f20.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>GitHub Actions is an integrated CI/CD tool in the GitHub platform that will help you build and do various tasks on your code and projects. By using these actions you can fully avoid hosting a build server as we used to back in the days. 😁</p><p>If you are ready, then let's move on to the stuff you are here for. 🔥</p><h2 id="your-first-workflow-with-github-actions">Your First Workflow With GitHub Actions</h2><p>Before we dive in to the good stuff, I wan't to highlight what the different words I will be using mean. A <strong>workflow</strong> at GitHub Actions is what you will know as a <strong>build process</strong>. A Workflow is <strong>described</strong> <strong>using</strong> <strong>YAML</strong>.</p><p>Workflow files can get rather complex as you start to dive deeper and deeper into how the build process is and each step that goes into your build process. Fear not! There are lots of templates you can use and tweak to fit on your project.</p><h3 id="an-easy-way">An Easy Way</h3><p>A guy named <a href="https://github.com/timheuer?ref=christian-schou.com" rel="noreferrer">Tim Heuer</a> has made a project named <code>dotnet-workflow</code> and it will help you build most .NET projects.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/timheuer/dotnet-workflow?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - timheuer/dotnet-workflow: Template for quickly creating a starting GitHub Actions workflow for a .NET Core app</div><div class="kg-bookmark-description">Template for quickly creating a starting GitHub Actions workflow for a .NET Core app - timheuer/dotnet-workflow</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/pinned-octocat-093da3e6fa40-3.svg" alt=""><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">timheuer</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/dotnet-workflow" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>According to Tim, who made the README for the project, it is:</p><blockquote class="kg-blockquote-alt">A simple global tool to give you a handy and quick method to create a GitHub Actions workflow file for continuous integration (CI) builds. </blockquote><p>To use it, you would have to install the package from <a href="https://www.nuget.org/packages/TimHeuer.GitHubActions.Templates/?ref=christian-schou.com" rel="noreferrer">NuGet</a> with the following command, as it's not available in <strong>dotnet</strong> by default.</p><pre><code class="language-powershell">dotnet new --install TimHeuer.GitHubActions.Templates</code></pre><p>When you have installed the package, you can invoke it from your terminal with the following command:</p><pre><code class="language-powershell">dotnet new workflow</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://christian-schou.com/content/images/2025/01/dotnet-new-workflow.webp" class="kg-image" alt="dotnet, github actions, dotnet workflow" loading="lazy" width="1382" height="274" srcset="https://christian-schou.com/content/images/size/w600/2025/01/dotnet-new-workflow.webp 600w, https://christian-schou.com/content/images/size/w1000/2025/01/dotnet-new-workflow.webp 1000w, https://christian-schou.com/content/images/2025/01/dotnet-new-workflow.webp 1382w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">dotnet new workflow</span></figcaption></figure><p>You should now have a YAML file in the <code>.github/workflows</code> folder with the name of your project folder. This YAML file will contain the steps required to build and test your .NET project/solution.</p><p>This is what you should have in your YAML file. You can safely remove the part with <code>Restore workloads</code> if you are not using <a href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview?ref=christian-schou.com" rel="noreferrer">Aspire</a>, <a href="https://webassembly.org/?ref=christian-schou.com" rel="noreferrer">Wasm</a> or <a href="https://learn.microsoft.com/en-us/dotnet/maui/what-is-maui?view=net-maui&ref=christian-schou.com" rel="noreferrer">Maui</a>.</p><pre><code class="language-yaml">name: "Build"

on:
  push:
    branches:
      - main
    paths-ignore:
      - '**/*.md'
      - '**/*.gitignore'
      - '**/*.gitattributes'
  pull_request:
    branches:
      - main
    paths-ignore:
      - '**/*.md'
      - '**/*.gitignore'
      - '**/*.gitattributes'
  workflow_dispatch:
      
jobs:
  build:
    name: Build 
    runs-on: ubuntu-latest
    env:
      DOTNET_CLI_TELEMETRY_OPTOUT: 1
      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
      DOTNET_NOLOGO: true
      DOTNET_GENERATE_ASPNET_CERTIFICATE: false
      DOTNET_ADD_GLOBAL_TOOLS_TO_PATH: false
      DOTNET_MULTILEVEL_LOOKUP: 0
      DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: true
      TERM: xterm

    steps:
    - uses: actions/checkout@v4
      
    - name: Setup .NET SDK
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: 9.0.101

    # if not using any workloads (e.g, Aspire, Wasm, Maui), remove this step
    - name: Restore workloads
      run: dotnet workload restore

    - name: Restore
      run: dotnet restore

    - name: Build
      run: dotnet build --configuration Release --no-restore

    - name: Test
      run: dotnet test</code></pre><p>Now go ahead and commit the file to your repository at GitHub. by default, it will run every time you are creating a new <strong>pull-request</strong> or <strong>push</strong> to your <code>main</code> branch.</p><p>In the image below you can see the actions in the workflow defined in the YAML file above, but being executed at GitHub Actions.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://christian-schou.com/content/images/2025/01/build-details-github-actions-dotnet.webp" class="kg-image" alt="dotnet build, github actions" loading="lazy" width="2000" height="862" srcset="https://christian-schou.com/content/images/size/w600/2025/01/build-details-github-actions-dotnet.webp 600w, https://christian-schou.com/content/images/size/w1000/2025/01/build-details-github-actions-dotnet.webp 1000w, https://christian-schou.com/content/images/size/w1600/2025/01/build-details-github-actions-dotnet.webp 1600w, https://christian-schou.com/content/images/size/w2400/2025/01/build-details-github-actions-dotnet.webp 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Build actions for .NET project at GitHub Actions</span></figcaption></figure><p>When the build is complete, you will have the option in the Summary to see warnings and the result of each step. In this workflow file we have only defined one step named Build, hence you will only see a Build step in the overview, as shown in the image below.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://christian-schou.com/content/images/2025/01/finished-build-github-actions-dotnet.webp" class="kg-image" alt="dotnet build, github actions, build summary" loading="lazy" width="2000" height="919" srcset="https://christian-schou.com/content/images/size/w600/2025/01/finished-build-github-actions-dotnet.webp 600w, https://christian-schou.com/content/images/size/w1000/2025/01/finished-build-github-actions-dotnet.webp 1000w, https://christian-schou.com/content/images/size/w1600/2025/01/finished-build-github-actions-dotnet.webp 1600w, https://christian-schou.com/content/images/size/w2400/2025/01/finished-build-github-actions-dotnet.webp 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Summary for finished .NET build with one Build step</span></figcaption></figure><p>That works pretty well, and it was super easy - if you ask me! 💪</p><h2 id="summary">Summary</h2><p>In this short DevOps tutorial on how to build and test .NET projects at GitHub Actions, you learned an easy way of creating a new workflow file to speed up your development process.</p><p>I am thinking of forking Tim's project and refactoring it a bit to become a CLI tool with extra options and features for creating these build templates for .NET projects. Let me know what you think in the comments below.</p><p>Until next time, happy coding! ✌️</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[How To Achieve High-Performance Logging in .NET]]></title>
                    <description><![CDATA[Traditional .NET logging can slow your app. Switch to LoggerMessage delegates for better performance or leverage source-generated logging (in .NET 6+) for minimal runtime costs and cleaner code. Boost efficiency, reduce overhead, and log like a pro! 🚀]]></description>
                    <link>https://christian-schou.com/blog/high-performance-logging-in-dotnet/</link>
                    <guid isPermaLink="false">677667b088f8f30001d558b5</guid>

                        <category><![CDATA[.NET]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Thu, 02 Jan 2025 12:26:00 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2025/01/high-performance-logging-dotnet-twc-1.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2025/01/high-performance-logging-dotnet-twc-1.webp" alt="How To Achieve High-Performance Logging in .NET"/> <p>Ever wondered if logging in .NET could be made more efficient? Efficient logging is crucial for modern software development. It helps monitor applications and aids in debugging and performance tuning.</p><p>In .NET, while the traditional extension methods for logging are common, there are better, more performant ways to log messages. In this short .NET post, I will show you these options and teach you how they can elevate your logging practices. 🔥</p><h2 id="traditional-logging">Traditional Logging</h2><p>Most .NET developers are familiar with the standard approach, like the one below.</p><pre><code class="language-csharp">logger.LogInformation("Processing request for {Endpoint}", endpoint);</code></pre><p>While pretty straightforward, this method can lead to performance issues due to the repeated parsing of message templates and the overhead of handling variable parameters. Modern .NET versions may even warn you about this, which is cool! ✌️</p><h3 id="ca1848use-loggermessage-delegates-for-improved-performance"><strong>CA1848 - Use LoggerMessage delegates for improved performance</strong></h3><p>An error/warning you might encounter in your IDE (<em>if enabled</em>) would be this one.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1848?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">CA1848: Use the LoggerMessage delegates (code analysis) - .NET</div><div class="kg-bookmark-description">Learn about code analysis rule CA1848: Use the LoggerMessage delegates</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-9.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">Youssef1313</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/dotnet-logo-1.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>If you don't have it but would like to enable this warning, you should update your <code>.csproj</code> file and change the analysis level to <code>latest-recommended</code>. That will automatically give you this warning.</p><pre><code class="language-xml">&lt;PropertyGroup&gt;
  &lt;AnalysisLevel&gt;latest-recommended&lt;/AnalysisLevel&gt;
&lt;/PropertyGroup&gt;</code></pre><p>So how can we make this warning go away and improve our software? 🤓</p><h2 id="unlocking-performance-with-loggermessage-delegates">Unlocking Performance with LoggerMessage Delegates</h2><p>Wo what does that message mean? Let's break it apart a bit.</p><p>.NET offers <code>LoggerMessage</code> delegates to improve the performance. How? .NET is capable of pre-compiling message templates and strongly type parameters. Here’s how you can achieve just that.</p><h3 id="step-1define-a-new-delegate">Step 1 - Define a new delegate</h3><p>The first thing we would have to do in our application would be to define a new delegate. In the example below I have created a new <code>ProcessRequestLog</code> template that I will be using whenever I log some information about processing an event in my application.</p><p>You can place it at any place in your application, but I would recommend you do it within the same project and inside a folder for all your delegates.</p><pre><code class="language-csharp">private static readonly Action&lt;ILogger, string, Exception?&gt; ProcessRequestLog =
    LoggerMessage.Define&lt;string&gt;(
        LogLevel.Information,
        new EventId(101, "ProcessRequest"),
        "Processing request for {Endpoint}"
    );</code></pre><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">🧑‍💻</div><div class="kg-callout-text">A delegate in .NET is essentially a type-safe function pointer. It allows you to encapsulate a method’s reference in an object, enabling the method to be passed as a parameter or assigned to a variable. In the context of <code spellcheck="false" style="white-space: pre-wrap;">LoggerMessage</code>, delegates help pre-define logging methods to improve performance and enforce consistency.</div></div><h3 id="step-2wrap-it">Step 2 - Wrap it</h3><p>Now that we have a new delegate in place, we should wrap it inside an extension method for our convenience. Here is the code for that:</p><pre><code class="language-csharp">public static void LogProcessRequest(this ILogger logger, string endpoint)
{
    ProcessRequestLog(logger, endpoint, null);
}</code></pre><h3 id="step-3use-it">Step 3 - Use it</h3><p>With the extension in place, we can now easily use it on the existing logger instance in our classes. Here how-to:</p><pre><code class="language-csharp">logger.LogProcessRequest("/api/users");</code></pre><p>By following the above approach we can eliminate the need to parse the message template every time the log is invoked, improving performance in high-throughput applications. ⚡️</p><h2 id="taking-it-one-step-further">Taking It One Step Further</h2><p>Beginning with .NET 6 we have the option to take advantage of source generators. What does that mean Christian and do I need it? Well if you want to make our logging even more efficient and developer-friendly then read along.</p><p>We are in such a lucky position that <code>LoggerMessage</code> is also an attribute 💪 In .NET we can use attributes to enable compile-time generation of our logging methods with little to minimal effort.</p><p>Here is how you can achieve just that.</p><h3 id="step-1declare-a-partial-method">Step 1 - Declare a partial method</h3><p>The first thing we have to do is declare a partial method for our <code>LogProcessRequest</code>. The code you need:</p><pre><code class="language-csharp">[LoggerMessage(
    EventId = 101,
    Level = LogLevel.Information,
    Message = "Processing request for {Endpoint}"
)]
public static partial void LogProcessRequest(this ILogger logger, string endpoint);</code></pre><p>With that in place, we can now continue in our business logic with the following as we did before:</p><pre><code class="language-csharp">logger.LogProcessRequest("/api/users");</code></pre><p>Why do it this way compared to the first way I showed you? With this approach, the logging logic is generated at compile time, reducing runtime overhead and boilerplate code in your application.</p><h2 id="comparing-the-two-approaches">Comparing The Two Approaches</h2><p>Interested in a high-level comparison of the two approaches? Here is a quick comparison for you.</p><table>
<thead>
<tr>
<th>Approach</th>
<th>Benefits</th>
<th>Drawbacks</th>
</tr>
</thead>
<tbody>
<tr>
<td>Traditional Logging</td>
<td>Simple and familiar</td>
<td>Performance overhead</td>
</tr>
<tr>
<td>LoggerMessage Delegates</td>
<td>High performance, strong typing</td>
<td>Requires additional boilerplate</td>
</tr>
<tr>
<td>Source-Generated Logging</td>
<td>Best performance, minimal code</td>
<td>Requires .NET 6 or later</td>
</tr>
</tbody>
</table>
<h2 id="pro-tips-for-better-logging">Pro Tips for Better Logging</h2><p>Here are my top three tips for improved logging in .NET applications.</p><ol><li><strong>Set Analysis Level</strong> - Enabling <code>latest-recommended</code> analyzers help identify areas for improvement directly in your IDE.</li><li><strong>Use Event IDs</strong> - Event IDs will make it easier to filter and identify logs at runtime.</li><li><strong>Leverage Source Generation</strong> - It’s the future of logging in .NET, reducing runtime costs - what's not to like?!</li></ol><h2 id="summary">Summary</h2><p>Modern .NET frameworks provide several tools for high-performance logging, from <code>LoggerMessage</code> delegates to source-generated logging.</p><p>By using these built-in methods, you can write cleaner, faster, and more maintainable logging code. I am sure you and your colleagues will thank you. I hope you learned something new in this short post. Until next time - Happy coding! ✌️</p><h2 id="resources">Resources</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Code analysis in .NET</div><div class="kg-bookmark-description">Learn about source code analysis in the .NET SDK.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-10.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">gewarren</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/dotnet-logo-2.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Introducing C# Source Generators - .NET Blog</div><div class="kg-bookmark-description">We’re pleased to introduce the first preview of Source Generators, a new C# compiler feature that lets C# developers inspect user code and generate new C# source files that can be added to a compilation. This is done via a new kind of component that we’re calling a Source Generator. To get started with Source […]</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/Microsoft-favicon-300x300.jpg" alt=""><span class="kg-bookmark-author">.NET Blog</span><span class="kg-bookmark-publisher">Phillip Carter</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/Picture1.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggermessage?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">LoggerMessage Class (Microsoft.Extensions.Logging)</div><div class="kg-bookmark-description">Creates delegates that can be later cached to log messages in a performant way.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-11.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">dotnet-bot</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/open-graph-image-4.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Use JsonSerializer to Pretty-Print JSON in .NET]]></title>
                    <description><![CDATA[Formatting JSON as plain text with proper indentation can make it much easier to include in emails or other readable contexts. Here&#x27;s a quick tip/guide on how to achieve that in .NET.]]></description>
                    <link>https://christian-schou.com/blog/use-jsonserializer-to-pretty-print-json-in-dotnet/</link>
                    <guid isPermaLink="false">675ade5a8589dd000105b8f0</guid>

                        <category><![CDATA[.NET Tips]]></category>
                        <category><![CDATA[.NET]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Wed, 11 Dec 2024 17:38:00 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/12/pretty-print-json-in-dotnet.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/12/pretty-print-json-in-dotnet.webp" alt="Use JsonSerializer to Pretty-Print JSON in .NET"/> <p>To pretty-print a JSON string in .NET and make it suitable for inclusion in an email, text page, a string, or any other thing, you can format the JSON with proper indentation and ensure it is styled as plain text for easy readability.</p><h2 id="code-example">Code Example</h2><p>Let's have a look at how we can implement this method on strings in our code.</p><pre><code class="language-csharp">using System;
using System.Text.Json;

class Program
{
    static void Main()
    {
        string jsonString = "{\"name\":\"Christian\",\"age\":28,\"country\":\"Denmark\"}";
        
        // Pretty-print the JSON string
        string prettyJson = PrettyPrintJson(jsonString);

        // Print the result
        Console.WriteLine(prettyJson);
    }

    static string PrettyPrintJson(string jsonString)
    {
        var jsonElement = JsonDocument.Parse(jsonString).RootElement;
        return JsonSerializer.Serialize(jsonElement, new JsonSerializerOptions { WriteIndented = true });
    }
}
</code></pre><p>What happens above? 🤔 Let me explain.</p><ol><li><strong>Parsing the JSON text</strong> - The <code>JsonDocument.Parse</code> method reads and parses the JSON string.</li><li><strong>Serialization with Indentation</strong> - The <code>JsonSerializer.Serialize</code> method outputs the JSON string with indentation by setting <code>WriteIndented</code> to <code>true</code> in the options.</li></ol><h2 id="as-an-extension-method">As An Extension Method</h2><pre><code class="language-csharp">using System.Text.Json;

namespace &lt;YourNameSpace-UPDATE-ME&gt;;

public static class StringExtensions
{   
    /// &lt;summary&gt;
    ///     Pretty prints a JSON string.
    /// &lt;/summary&gt;
    /// &lt;param name="jsonString"&gt;&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public static string PrettyPrintJson(this string jsonString)
    {
        JsonElement jsonElement = JsonDocument.Parse(jsonString).RootElement;
        return JsonSerializer.Serialize(jsonElement, new JsonSerializerOptions { WriteIndented = true });
    }
}</code></pre><p>With this, you can easily extend logic on your default strings to return a string in JSON format in a pretty way. 👌</p><p>I hope you solved a problem in your code with this short tutorial. If you have any questions, please let me know in the comments below. ✌️</p><h2 id="resources">Resources</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/api/system.text.json?view=net-9.0&ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">System.Text.Json Namespace</div><div class="kg-bookmark-description">Provides high-performance, low-allocating, and standards-compliant capabilities to process JavaScript Object Notation (JSON), which includes serializing objects to JSON text and deserializing JSON text to objects, with UTF-8 support built-in. It also provides types to read and write JSON text encoded as UTF-8, and to create an in-memory document object model (DOM) for random access of the JSON elements within a structured view of the data.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-8.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">dotnet-bot</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/open-graph-image-3.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Use nameof to Keep Code Refactoring-Safe]]></title>
                    <description><![CDATA[Using nameof in .NET helps make code more maintainable and resilient to refactoring. It returns names as strings tied to code directly, improving readability, refactoring safety, and compile-time error checking.]]></description>
                    <link>https://christian-schou.com/blog/use-nameof-to-keep-code-refactoring-safe/</link>
                    <guid isPermaLink="false">6732ef9d8589dd000105b7e2</guid>

                        <category><![CDATA[.NET Tips]]></category>
                        <category><![CDATA[.NET]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Mon, 18 Nov 2024 05:40:45 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/11/use-nameof-to-keep-code-refactoring-safe-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/11/use-nameof-to-keep-code-refactoring-safe-twc.webp" alt="Use nameof to Keep Code Refactoring-Safe"/> <p>When you are writing .NET code, there is one thing I enjoy using to make my code more maintainable (<em>and who doesn't like that?!</em> 😅) and resilient to errors, is the use of the <code>nameof</code> operator.</p><p>The <code>nameof</code> operator returns the variable, type, or member as a string and nothing else. The genius thing here is that it has been tied strictly/directly to the code where it's being used, making our development much safer if we decide to refactor it.</p><h2 id="what-is-nameof">What Is nameof?</h2><p>According to Microsoft Learn, <code>nameof</code> is the following:</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">💡</div><div class="kg-callout-text">A&nbsp;<code spellcheck="false" style="white-space: pre-wrap;">nameof</code>&nbsp;expression produces the name of a variable, type, or member as the string constant. A&nbsp;<code spellcheck="false" style="white-space: pre-wrap;">nameof</code>&nbsp;expression is evaluated at compile time and has no effect at run time. - <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof?ref=christian-schou.com" rel="noreferrer">Microsoft</a>.</div></div><h2 id="why-use-nameof-%F0%9F%A4%94">Why Use nameof? 🤔 </h2><p>So why exactly should I use the <code>nameof</code> operator in my code, Christian? I am glad you asked. Here are a few reasons why I think you should do it and encourage your colleagues and other engineers to do the same.</p><ul><li><strong>👓 Readability</strong> - You can make it super clear that the value is derived from a specific variable or method, which will make your code easier to understand and as I just mentioned, refactor/maintain.</li><li><strong>🧑‍💻 Refactoring</strong> - We have all been there, hardcoding strings... When you rename variables your methods could potentially break because it's referencing other code across your code base. When you implement nameof, your IDE will automatically update all references when you rename stuff, making it a breeze to implement changes in your code.</li><li>⚙️<strong> Compiling</strong> - When you are compiling your code, you will get what I call compile-time checking. Your compiler will tell you about errors if you try to compile code where you have used a non-existing member in <code>nameof</code>. That will be another great tool in preventing compile-time errors and catching mistakes earlier.</li></ul><h2 id="how-to-use-nameof-%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB">How To Use nameof? 🧑‍💻</h2><p>To demonstrate what I just talked about, I have created a small practical example you can reference to see how easy it is to use the <code>nameof</code> operator in your own code.</p><pre><code class="language-csharp">public class MyClass
{
    public string MyProperty { get; set; }

    public void LogPropertyChange()
    {
        Console.WriteLine($"{nameof(MyProperty)} has changed.");
    }
}
</code></pre><p><strong>What happens above? 🤓</strong></p><p>In the code above, <code>nameof(MyProperty)</code> outputs <code>"MyProperty"</code> at runtime. If you ever should rename <code>MyProperty</code>, the <code>nameof</code> reference updates automatically, and will prevent your compiler from complaining and errors/bugs will be equal to 0. 🐛</p><h2 id="resources">Resources</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">The nameof expression - evaluate the text name of a symbol - C# reference</div><div class="kg-bookmark-description">The C# `nameof` expression produces the name of its operand. You use it whenever you need to use the name of a symbol as text</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-6.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">BillWagner</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/logo_csharp-1.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Enhance String Semantics with the StringSyntax Attribute]]></title>
                    <description><![CDATA[Learn how to use the StringSyntax attribute in .NET to clarify string formats like DateTime, JSON, or Regex, enhancing readability, IDE support, and reducing errors when writing .NET or C# code ✌️]]></description>
                    <link>https://christian-schou.com/blog/enhance-semantics-with-stringsyntax-attribute/</link>
                    <guid isPermaLink="false">6731a9db8589dd000105b76e</guid>

                        <category><![CDATA[.NET Tips]]></category>
                        <category><![CDATA[.NET]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Mon, 11 Nov 2024 05:31:00 +0100</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/11/Enhance-String-Semantics-with-the-StringSyntax-Attribute.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/11/Enhance-String-Semantics-with-the-StringSyntax-Attribute.webp" alt="Enhance String Semantics with the StringSyntax Attribute"/> <p>The <code>StringSyntax</code> attribute was added in <strong>.NET 7</strong>, so let's just say it's a relatively new feature (<em>at the time of writing</em>). This new attribute <strong>allows you to specify the intended syntax or format of a string</strong>.</p><p>Having a feature like this can make our code more readable and safer. In this short .NET tip, I will show you how it works and why it is useful.</p><h2 id="what-is-stringsyntax-%F0%9F%A4%94">What Is StringSyntax? 🤔</h2><p>The <code>StringSyntax</code> attribute can only be applied to string parameters, properties, or fields in .NET. We do this to indicate to other developers (<em>and ourselves</em> 😅) the specific format or syntax that we expect the string to be within.</p><p>Ever heard of code analyzers? Well... by adding the StringSyntax attribute we can give them a hint about the semantic meaning of a string we are expecting. This can help with easier validation and give developers a better experience due to improved IDE support. I am talking about syntax highlighting here.</p><h2 id="why-use-stringsyntax-%F0%9F%92%AD">Why Use StringSyntax? 💭</h2><p>You probably already have a good idea of why this is a good thing to make use of 😄 but here are my top three reasons why I think you should start using it.</p><ol><li><strong>Better readability</strong> - By indicating that a string follows a specific syntax (e.g., <code>Json</code>, <code>Regex</code>, or <code>Uri</code>), you can make the intended purpose of the string clearer to anyone reading, refactoring, or consuming the code. ✅</li><li><strong>IDE support</strong> - <a href="https://visualstudio.microsoft.com/?ref=christian-schou.com" rel="noreferrer">Visual Studio</a> and other IDEs like <a href="https://www.jetbrains.com/rider/?ref=christian-schou.com" rel="noreferrer">Rider</a> can use the attribute to give the developer a syntax highlighting, autocompletion, and even validation on the string. What's not to like here!? 👌</li><li><strong>Minimize the risk of errors</strong> - By indicating a specific syntax for your string, you can avoid misinterpretation errors. Example - Imagine a method might expect a JSON-formatted string rather than plain text, and the attribute can help ensure the correct format.</li></ol><h2 id="how-to-use-stringsyntax-%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB">How to Use StringSyntax 🧑‍💻</h2><p>No .NET tip without a demonstration 🧑‍💻 Here is a quick example of how you can use <code>StringSyntax</code> in your code or get an idea of how it's implemented. It's luckily pretty straightforward.</p><pre><code class="language-csharp">using System.Diagnostics.CodeAnalysis;

public class ExampleClass
{
    public void ConfigureRegexPattern([StringSyntax(StringSyntaxAttribute.Regex)] string regexPattern)
    {
        // Code that uses regexPattern with the assumption it's a valid regular expression
    }

    public void SetJsonString([StringSyntax(StringSyntaxAttribute.Json)] string jsonString)
    {
        // Code that processes jsonString as JSON
    }
}
</code></pre><h3 id="available-syntax-options-%E2%9C%85">Available Syntax Options ✅</h3><p>The <code>StringSyntax</code> attribute class provides several common syntax types, you can read more about those in the link below in my resources for this .NET tip.</p><ul><li><code>Regex</code> - Marks the string as a <strong>regular expression</strong> pattern.</li><li><code>Json</code> - Indicates the string should follow <strong>JSON</strong> formatting.</li><li><code>DateTimeFormat</code> - Indicates the string should follow <code>DateTime</code> formatting.</li><li><code>Uri</code> - Indicates a <code>URI</code> format is expected.</li><li><code>Xml</code> - Indicates <strong>XML</strong> format.</li></ul><h2 id="use-cases-%E2%9A%A1%EF%B8%8F">Use Cases ⚡️</h2><p>I have tried to think about some use cases for this attribute, and here are my thoughts so far for when I would use it myself.</p><div class="kg-card kg-toggle-card" data-kg-toggle-state="close">
            <div class="kg-toggle-heading">
                <h4 class="kg-toggle-heading-text"><span style="white-space: pre-wrap;">API Design</span></h4>
                <button class="kg-toggle-card-icon" aria-label="Expand toggle to read content">
                    <svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                        <path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"></path>
                    </svg>
                </button>
            </div>
            <div class="kg-toggle-content"><p dir="ltr"><span style="white-space: pre-wrap;">Helps clarify what specific string values are intended for in APIs, especially for complex formats like JSON or regular expressions.</span></p></div>
        </div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close">
            <div class="kg-toggle-heading">
                <h4 class="kg-toggle-heading-text"><span style="white-space: pre-wrap;">Tooling and Analyzers</span></h4>
                <button class="kg-toggle-card-icon" aria-label="Expand toggle to read content">
                    <svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                        <path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"></path>
                    </svg>
                </button>
            </div>
            <div class="kg-toggle-content"><p dir="ltr"><span style="white-space: pre-wrap;">Can help static analysis tools provide warnings if the string doesn’t match the expected format.</span></p></div>
        </div><div class="kg-card kg-toggle-card" data-kg-toggle-state="close">
            <div class="kg-toggle-heading">
                <h4 class="kg-toggle-heading-text"><span style="white-space: pre-wrap;">Improved Developer Experience</span></h4>
                <button class="kg-toggle-card-icon" aria-label="Expand toggle to read content">
                    <svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                        <path class="cls-1" d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311"></path>
                    </svg>
                </button>
            </div>
            <div class="kg-toggle-content"><p dir="ltr"><span style="white-space: pre-wrap;">Improves the development experience with IntelliSense and validation support, leading to fewer bugs and better self-documenting code.</span></p></div>
        </div><h2 id="resources-%F0%9F%93%9A">Resources 📚</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.stringsyntaxattribute?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">StringSyntaxAttribute Class (System.Diagnostics.CodeAnalysis)</div><div class="kg-bookmark-description">Specifies the syntax used in a string.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-5.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">dotnet-bot</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/open-graph-image-1.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[LXC vs Docker - What To Choose?]]></title>
                    <description><![CDATA[Building software is challenging, but ensuring it runs smoothly across different environments can be even more complex. Containers have become an essential solution for this, offering a streamlined way to package and deploy software reliably.]]></description>
                    <link>https://christian-schou.com/blog/lxc-vs-docker-what-to-choose/</link>
                    <guid isPermaLink="false">670a2066d579200001357112</guid>

                        <category><![CDATA[Docker]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Tue, 15 Oct 2024 05:54:00 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/10/lxc-vs-docker-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/10/lxc-vs-docker-twc.webp" alt="LXC vs Docker - What To Choose?"/> <p>Creating software in the modern world can be complex, but running it can be even more complicated as there are many requirements for supporting it. To fix that we can use containers, which have become a go-to tool for developers crafting modern software.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.docker.com/resources/what-container/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">What is a Container? | Docker</div><div class="kg-bookmark-description">A container is a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another. A Docker container image is a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries and settings.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/cropped-docker-logo-favicon-270x270.png" alt=""><span class="kg-bookmark-author">Docker</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/docker-default-meta-image-1110x583.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>If you are new to containers, it can be quite overwhelming... Where to start and what to use?! 🤔 We have several options available, but two of the most popular technologies are Linux Containers (<em>LXC</em>) and Docker Containers. Don't know the difference? Perfect! You have come to the right place 🔥</p><p>In this post, I will discuss the differences between LXC- and Docker containers. By the end of the post, you will have a much better understanding of what both technologies are capable of and know how to pick the right platform for your software project. 🧑‍💻</p><h2 id="%F0%9F%90%A7-lxclinux-containers">🐧 LXC - Linux Containers</h2><p><strong>LXC</strong> is short for Linux Containers, I will use <strong>LXC</strong> in the rest of the post when describing Linux Containers.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://linuxcontainers.org/lxc/introduction/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Linux Containers - LXC - Introduction</div><div class="kg-bookmark-description">The umbrella project behind Incus, LXC, LXCFS, Distrobuilder and more.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-3.ico" alt=""><span class="kg-bookmark-author">Linux containers logo</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/containers.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><h3 id="what-is-lxc">What Is LXC?</h3><p>LXC is an advanced virtualization technology engineers can use to create a lightweight but still efficient isolated environment with key features from the Linux kernel.</p><p>It's perfect for running more than one application in a single host system. It uses the kernel's <code>cgroups</code> (<em>control groups</em>) and namespaces features to provide lightweight, virtualized environments that share the host's kernel but can behave like independent systems. Cool right?! 🤌 So what does it look like from 20.000 thousand feet? ⬇️</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://christian-schou.com/content/images/2024/10/linux-container-architecture-twc.webp" class="kg-image" alt="lxc, linux containers, containers, vm in container" loading="lazy" width="1920" height="1080" srcset="https://christian-schou.com/content/images/size/w600/2024/10/linux-container-architecture-twc.webp 600w, https://christian-schou.com/content/images/size/w1000/2024/10/linux-container-architecture-twc.webp 1000w, https://christian-schou.com/content/images/size/w1600/2024/10/linux-container-architecture-twc.webp 1600w, https://christian-schou.com/content/images/2024/10/linux-container-architecture-twc.webp 1920w" sizes="(min-width: 1200px) 1200px"><figcaption><span style="white-space: pre-wrap;">LXC Environment</span></figcaption></figure><h3 id="common-use-cases-of-lxc">Common Use Cases Of LXC</h3><p>LXC can be used for a lot of things, but some of the most common use cases I have seen and normally see it being used for are: </p><ul><li><strong>Application Isolation</strong> - You can run applications in an isolated environment to avoid conflicts.</li><li><strong>DevOps &amp; Testing</strong> - As a DevOps Engineer you have the option to test software in different Linux distributions or configurations without needing multiple physical or virtual machines.</li><li><strong>Lightweight Virtualization</strong> - With LXC you can deploy <strong>multiple services</strong> on a <strong>single host</strong> while ensuring security and resource control.</li></ul><p>LXC was originally released as an open-source project and became a part of Linux distributions like Ubuntu, <a href="https://www.debian.org/index.en.html?ref=christian-schou.com" rel="noreferrer">Debian</a>, and others. <a href="https://canonical.com/?ref=christian-schou.com" rel="noreferrer">Canonical</a>, the makers of <a href="https://ubuntu.com/?ref=christian-schou.com" rel="noreferrer">Ubuntu</a>, played a significant role in the popularization of LXC because they baked it into their releases and helped improve its usability.</p><h3 id="%E2%9C%85when-lxc-may-be-the-right-choice">✅ - When LXC May Be The Right Choice</h3><p>LXC can be a great choice in several scenarios (<em>it always comes down to your specific requirements</em>), but I have outlined some key points below you should ask yourself when you need to choose a platform for running your application.</p><h4 id="when-full-system-containers-are-needed">When Full System Containers Are Needed</h4><p>LXC is focused on running a complete operating system inside a container, rather than just the application itself. This is why LXC is a great choice when you need the option to manage the entire OS and not just an app.</p><p>In many companies a lot of legacy applications make the foundation for their business and modernization is too expensive. LXC would be a great choice here as you don't have to containerize the application, but just the OS where the application is running. This way you can support legacy systems with their environment-specific services and configurations.</p><h4 id="resource-constrained-environments">Resource-Constrained Environments</h4><p>If you need an operating system just to run one or a few applications, but don't have the hardware for it, LXC would be a good fit. Linux Containers are more lightweight compared to a traditional virtual machine. This makes it a perfect choice when you are limited on CPU, memory, and storage.</p><p>In cases where you need to run a lot of machines on a single machine, you could achieve what is referred to as high-density hosting. Because of the lightweight architecture, you can host more containers than you would be able to with virtual machines. The most awesome part of that, which I like is that we can reduce overhead and increase efficiency on our servers.</p><h4 id="need-for-fine-grained-control-of-your-environment">Need for Fine-Grained Control Of Your Environment</h4><p>Looking for a way to be in full control of networking, storage, resource limits, and access to the host system's hardware? This can be achieved with LXC. Here you can customize those settings at another level than you are allowed to with e.g. Docker.</p><p>A thing I think is awesome is how much performance you can achieve with LXC. Since Linux Containers share the host kernel directly (<em>without too much abstraction</em> 😅), it's often a preferred choice if you need a system where you have high performance with full control of the resources underneath.</p><h4 id="when-system-isolation-is-important">When System Isolation is Important</h4><p>As I mentioned earlier in my introduction to LXC, you can make use of <code>namespaces</code> and <code>cgroups</code>.</p><div class="kg-card kg-callout-card kg-callout-card-white"><div class="kg-callout-emoji">🛡️</div><div class="kg-callout-text">Namespaces and cgroups gives you a perfect way of creating total isolation between your containers. This is great when you need an environment where the processes, files and networks in one system is isolated from the other containers.</div></div><p>A great use case would be a multi-tenant environment, where you need to ensure full/strict isolation between services.</p><p>I have used it professionally for test environments. From my DevOps days, I used LXC to create isolated environments where the engineers would be able to test their software on multiple Linux distributions without having to configure a whole new virtual machine or actual physical server every time.</p><h4 id="enterprise-and-production-environments">Enterprise and Production Environments</h4><p>If you need an environment for a stable and long-term project that potentially could run for months or maybe years, LXC would be a great fit! It would handle your production workloads without frequent changes perfectly!</p><p>One thing I think is very important to highlight is the maturity of LXC. It's a very well-tested technology and has proven to be able to run without problems in large environments. This is also one of the reasons why it would be a great choice if you are looking for a solution that would be enterprise-worthy and something you could count on.</p><h3 id="%E2%9D%8Cwhen-lxc-may-not-be-the-right-choice">❌ - When LXC May Not Be The Right Choice </h3><p>I would not recommend using Linux Containers if you are planning to deploy a microservice platform or containerized applications. Applications like those tend to be rapidly evolving and need a lot of redeployments. In that case, you should go with Docker or Kubernetes as they would provide more tooling and be more developer-friendly.</p><p>LXC is not a tool that is easy to use compared to Docker. If you want a system where you can make quick deployments and manage with a few commands, Linux Containers are not the right choice. You need some more Linux skills and be able to understand how the OS works.</p><div class="kg-card kg-signup-card kg-width-wide " data-lexical-signup-form="" style="background-color: #000000; display: none;">
            
            <div class="kg-signup-card-content">
                
                <div class="kg-signup-card-text kg-align-center">
                    <h2 class="kg-signup-card-heading" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">Sign up for Tech with Christian</span></h2>
                    <p class="kg-signup-card-subheading" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">A Pragmatic Solution Architect &amp; Software Engineer sharing his knowledge with the internet.</span></p>
                    
        <form class="kg-signup-card-form" data-members-form="signup">
            <input data-members-label="" type="hidden" value="Docker">
            <div class="kg-signup-card-fields">
                <input class="kg-signup-card-input" id="email" data-members-email="" type="email" required="true" placeholder="Your email">
                <button class="kg-signup-card-button kg-style-accent" style="color: #FFFFFF;" type="submit">
                    <span class="kg-signup-card-button-default">Subscribe</span>
                    <span class="kg-signup-card-button-loading"><svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
        <g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper">
            <g class="nc-loop-dots-4-24-icon-o">
                <circle cx="4" cy="12" r="3"></circle>
                <circle cx="12" cy="12" r="3"></circle>
                <circle cx="20" cy="12" r="3"></circle>
            </g>
            <style data-cap="butt">
                .nc-loop-dots-4-24-icon-o{--animation-duration:0.8s}
                .nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite}
                .nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:-.3s;animation-delay:calc(var(--animation-duration)/-2.666)}
                .nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:-.15s;animation-delay:calc(var(--animation-duration)/-5.333)}
                .nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px}
                @keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}
            </style>
        </g>
    </svg></span>
                </button>
            </div>
            <div class="kg-signup-card-success" style="color: #FFFFFF;">
                Email sent! Check your inbox to complete your signup.
            </div>
            <div class="kg-signup-card-error" style="color: #FFFFFF;" data-members-error=""></div>
        </form>
        
                    <p class="kg-signup-card-disclaimer" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">No spam. Unsubscribe anytime. Totally free!</span></p>
                </div>
            </div>
        </div><h2 id="%F0%9F%90%B3-docker">🐳 Docker</h2><p><a href="https://www.docker.com/?ref=christian-schou.com" rel="noreferrer">Docker</a> is an open-source software platform that helps Software Developers, DevOps, and IT operations simplify the process of developing and managing applications. When using docker you can package an application into a container and distribute it to millions of servers.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://christian-schou.com/blog/what-is-docker-a-docker-beginner-guide/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">What is Docker? Docker beginner guide - Christian Schou</div><div class="kg-bookmark-description">Get started with Docker and learn how to create your first container running Nginx and serve your own webpage written in HTML. Docker is a very powerful tool to run applications without having to install a lot of dependencies.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/cropped-cs-logo-color-retina-1.png" alt=""><span class="kg-bookmark-author">Christian Schou | Tech with Christian</span><span class="kg-bookmark-publisher">Christian Schou</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/docker-featured-image.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>I won't be diving that much into what Docker is in this post. You are more than welcome to read my article above or visit the official Docker website to learn more. Since you are reading this article, I am pretty sure though, that you know what Docker is. In case you don't here is a quick drawing of what the architecture would look like in a production environment.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://christian-schou.com/content/images/2024/10/docker-container-setup-twc.webp" class="kg-image" alt="docker, docker architecture" loading="lazy" width="1920" height="1080" srcset="https://christian-schou.com/content/images/size/w600/2024/10/docker-container-setup-twc.webp 600w, https://christian-schou.com/content/images/size/w1000/2024/10/docker-container-setup-twc.webp 1000w, https://christian-schou.com/content/images/size/w1600/2024/10/docker-container-setup-twc.webp 1600w, https://christian-schou.com/content/images/2024/10/docker-container-setup-twc.webp 1920w" sizes="(min-width: 1200px) 1200px"><figcaption><span style="white-space: pre-wrap;">Docker Environment</span></figcaption></figure><p>So let's skip to the part we are all here for. How can Docker improve LXC, and when is it a better choice?</p><h3 id="how-docker-simplifies-and-enhances-lxc-for-modern-containerization">How Docker Simplifies and Enhances LXC for Modern Containerization</h3><p>Docker <strong>enhances LXC by simplifying container management</strong> through an easy-to-use interface, automating tasks that would require manual setup in LXC. This is a game-changer to me!</p><p>It also introduces efficient image management, utilizing a layered filesystem and providing access to prebuilt images via Docker Hub, which streamlines the sharing and updating of containers. Docker Hub is normally used for public images, but you can sign up for an account and have the option to host private images as well.</p><p>Additionally, Docker extends support to multiple platforms, including Windows and macOS, and offers built-in orchestration tools like Docker Compose and Swarm, making it easier to deploy and manage multi-container applications compared to LXC.</p><h3 id="key-benefits-of-docker">Key Benefits of Docker</h3><p>Docker offers several key benefits that make it a popular choice for modern application development, please note the word <strong>modern</strong>! Why? 🤔 Docker ensures <strong>portability</strong> by allowing containers to run consistently across different environments. </p><p>Have you ever heard an engineer say this ⬇️</p><blockquote class="kg-blockquote-alt">it works on my machine</blockquote><p>One of the awesome benefits of using Docker is that we can minimize the "<em>it works on my machine</em>" problem - hallelujah 👏 I am so tired of that phrase... Docker's <strong>efficiency</strong> comes from its lightweight nature, which leads to faster startup times ⚡ and better resource utilization compared to traditional virtual machines 👌.</p><p>It also supports <strong>scalability</strong>, making it easy to manage and deploy multi-container applications with tools like <a href="https://docs.docker.com/engine/swarm/?ref=christian-schou.com" rel="noreferrer">Docker Swarm</a> and <a href="https://kubernetes.io/?ref=christian-schou.com" rel="noreferrer">Kubernetes</a>. </p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://christian-schou.com/blog/getting-started-with-docker-swarm/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">#1 Docker Swarm Tutorial - Orchestration &amp; Management 🐳</div><div class="kg-bookmark-description">Learn Docker Swarm - #1 guide to master the art of container orchestration and management. Deploy, scale, monitor, and manage services with ease.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/cropped-cs-logo-color-retina-2.png" alt=""><span class="kg-bookmark-author">Christian Schou | Tech with Christian</span><span class="kg-bookmark-publisher">Christian Schou</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/docker-swarm-guide-tech-with-christian-2.webp" alt="" onerror="this.style.display = 'none'"></div></a></figure><p>Additionally, Docker provides <strong>consistency</strong> across development, testing, and production environments, while its great <a href="https://www.docker.com/products/?ref=christian-schou.com" rel="noreferrer"><strong>ecosystem</strong></a>—including Docker Hub and integrations with CI/CD pipelines—further streamlines the development process.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://hub.docker.com/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Docker Hub Container Image Library | App Containerization</div><div class="kg-bookmark-description">Welcome to the world’s largest container registry built for developers and open source contributors to find, use, and share their container images. Build, push and pull.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-4.ico" alt=""><span class="kg-bookmark-author">App Containerization</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/C080RJW1.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><h3 id="top-use-cases-for-docker">Top Use Cases for Docker</h3><p>I could come up with 10+ use cases for picking Docker as a tool, but that would end up in a wall of text and no one would read that. Instead, I have picked my top four use cases of why you should use Docker.</p><ol><li><strong>Microservices</strong> - Docker is perfect for deploying applications built on the <a href="https://microservices.io/?ref=christian-schou.com" rel="noreferrer">microservices architecture</a>, allowing each service to be <strong>developed</strong>, <strong>scaled</strong>, and <strong>managed</strong> independently.</li><li><strong>CI/CD Pipelines</strong> - It streamlines continuous integration and deployment by providing consistent environments for <strong>building</strong>, <strong>testing</strong>, and <strong>releasing</strong> code, reducing integration issues and speeding up deployment. ⚡</li><li><strong>Development and Testing</strong> - Docker enables quick setup of isolated environments that will mirror your production environment, ensuring consistent application behavior across all machines and minimizing bugs during deployment. What's not to like here?! 😄</li><li><strong>Cloud Migration</strong> - Docker simplifies the migration of applications to the cloud or the creation of hybrid environments, ensuring compatibility and <strong>reducing vendor lock-in</strong> - a thing I appreciate! Don't like your hosting provider? No problem, I will just take my images and find another home for my platform.</li></ol><h3 id="where-docker-may-not-ne-the-right-choice">Where Docker May Not Ne The Right Choice</h3><p>While Docker is a powerful tool for <strong>modern application development</strong>, there are some scenarios where it may not be the best tool. I have tried to boil it down to three bullet points I think are some key takeaways for you in this post, for when you should not use Docker.</p><ul><li><strong>Heavyweight Virtualization Needs</strong> - Avoid Docker if you need to run full operating systems with dedicated resources.</li><li><strong>Legacy Applications</strong> - If your applications are tightly coupled with the underlying OS or require specific configurations, Docker may complicate deployment rather than simplify it. 😅</li><li><strong>High-Performance or Real-Time Applications</strong> - For applications that require low latency or high-performance computing, the overhead introduced by Docker could hinder performance compared to bare-metal setups or traditional virtualization.</li></ul><h2 id="%F0%9F%86%9A-comparing-lxc-and-linux">🆚 Comparing LXC and Linux</h2><p>Let's take a high-level but still detailed view of LXC vs Docker. I have tried to narrow down their key features and differences in the table below. This can be a go-to place for you when you need to figure out what technology you should choose for your project.</p>
<!--kg-card-begin: html-->
<table>
  <thead>
    <tr>
      <th>⚡️ Feature</th>
      <th>🐧 LXC (Linux Containers)</th>
      <th>🐳 Docker</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Abstraction Level</strong></td>
      <td>Low-level container management</td>
      <td>High-level container management</td>
    </tr>
    <tr>
      <td><strong>Use Case</strong></td>
      <td>System-level virtualization; <br>lightweight VMs</td>
      <td>Application containerization</td>
    </tr>
    <tr>
      <td><strong>Image Management</strong></td>
      <td>Requires manual image creation <br>and management</td>
      <td>Uses Dockerfiles for automated <br>builds; Docker Hub for image sharing</td>
    </tr>
    <tr>
      <td><strong>Networking</strong></td>
      <td>More manual networking setup <br>required</td>
      <td>Built-in networking features with <br>easy configuration and service discovery</td>
    </tr>
    <tr>
      <td><strong>Portability</strong></td>
      <td>Limited portability; typically <br>Linux-only</td>
      <td>Highly portable; runs on multiple <br>platforms (Windows, macOS, Linux)</td>
    </tr>
    <tr>
      <td><strong>Isolation</strong></td>
      <td>Strong isolation using Linux <br>namespaces and cgroups</td>
      <td>Uses namespaces for isolation, with <br>added features like AppArmor and Seccomp</td>
    </tr>
    <tr>
      <td><strong>Performance</strong></td>
      <td>More efficient for system-level tasks</td>
      <td>Optimized for application performance, <br>with lightweight containers</td>
    </tr>
    <tr>
      <td><strong>Scaling</strong></td>
      <td>Manual scaling and orchestration</td>
      <td>Native support for scaling with <br>Docker Swarm and Kubernetes</td>
    </tr>
    <tr>
      <td><strong>Ecosystem</strong></td>
      <td>Less mature ecosystem, primarily <br>command-line tools</td>
      <td>Rich ecosystem with GUI tools, <br>CI/CD integration, and extensive <br>community support</td>
    </tr>
    <tr>
      <td><strong>Development Workflow</strong></td>
      <td>More complex setup for development <br>and testing environments</td>
      <td>Simplified workflow with Docker <br>Compose for managing multi-container <br>applications</td>
    </tr>
    <tr>
      <td><strong>Use of Filesystems</strong></td>
      <td>Standard filesystem usage</td>
      <td>Uses a layered filesystem (UnionFS) <br>for efficient storage and reuse</td>
    </tr>
    <tr>
      <td><strong>Container Management</strong></td>
      <td>Requires manual configuration <br>and management</td>
      <td>Simplified management using Docker <br>commands and orchestration tools</td>
    </tr>
    <tr>
      <td><strong>Security Features</strong></td>
      <td>Basic security through Linux <br>kernel features</td>
      <td>Advanced security options including <br>container isolation, network security, <br>and image signing</td>
    </tr>
  </tbody>
</table>

<!--kg-card-end: html-->
<h2 id="%F0%9F%93%9D-summarychoosing-between-lxc-and-docker">📝 Summary - Choosing Between LXC and Docker</h2><p>Software development is moving fast, like really fast! LXC and Docker serve different purposes and are tools we can use to help us in the crafting of software.</p><ul><li>If you are in the market for a <strong>lightweight virtualization</strong> platform where you can <strong>run a full operating system</strong> or <strong>support your legacy applications</strong>, LXC will be your go-to.</li><li>If you are developing modern software and need CI/CD pipelines, easy spin-up of environments, etc... Docker will be the perfect choice. It has everything baked directly into it in terms of management and gives you the option to easily port your applications to other environments.</li></ul><p>In the end, I can only say it all comes down to your specific requirements and use cases, but understanding the strengths of each technology can help you make an informed choice. I hope you learned a thing or two in this post about LXC and Docker. If you have any questions, let me know below. ✌️</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[How Port Mapping Works In Docker Compose]]></title>
                    <description><![CDATA[Learn how to use Docker Compose port mapping to manage container networking, with examples on basic mappings, multiple ports, port ranges, and advanced configurations. Perfect for solving container communication issues in any environment!]]></description>
                    <link>https://christian-schou.com/blog/how-port-mapping-works-in-docker-compose/</link>
                    <guid isPermaLink="false">65b21326ae10cc0001e25d4f</guid>

                        <category><![CDATA[Docker]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Wed, 09 Oct 2024 05:10:00 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/10/docker-port-mapping-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/10/docker-port-mapping-twc.webp" alt="How Port Mapping Works In Docker Compose"/> <p>When working with containers, we often (<em>in 99% of all cases</em>) need a way to access our container software to interact with it. But running multiple of the same containers in the same environment would cause some troubles... it would not be possible.</p><p>To solve that issue, Docker has something called port mapping, and that allows us to translate one port to another when configuring our containers.</p><p>In this short post, I will tell you about how Docker Compose port mapping works, with some YAML examples and illustrations. If you are ready, then let's get to it. 😎</p><h2 id="tldr-%F0%9F%9A%80">TL;DR 🚀</h2><p>When talking Docker and ports, a port is something we use to provide an option for communicating with an endpoint to exchange data between the host and a container. In other words - ports make it possible to send and receive data/information to and from a containerized application.</p><p>In Docker Compose, the ports of a service can be defined and mapped using the ports property in with YAML with the following syntax: <code>[host:]container[/protocol]</code> like shown below.</p><pre><code class="language-yaml">version: '3.3'

services:
  web-app:
    image: cool-project/web-app:latest
    ports:
      - 80:8080

...</code></pre><p>If you wanted to, you could also manage the protocols to be either <code>TCP</code> or <code>UDP</code>, depending on your application. Here is an example of what it would look like:</p><pre><code class="language-yaml">version: '3.3'

services:
  web-app:
    image: cool-project/web-app:latest
    ports:
      - 80:8080/tcp

...</code></pre><p>My example above shows that <code>TCP</code> port <code>80</code> at the Docker Host is mapped to the <code>TCP</code> port <code>8080</code> of the <code>web-app</code> container. This means that the docker image used for the container listens to requests on port <code>8080</code>, but we can send a request to port <code>80</code> and then receive the information from the container.</p><p>If you want to see how it looks with an illustration, then check my image below, where I have tried to make a drawing that shows what is going on.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://christian-schou.com/content/images/2024/01/port-mapping-docker-twc_1.webp" class="kg-image" alt="docker port mapping, port mapping, docker, docker compose" loading="lazy" width="1209" height="499" srcset="https://christian-schou.com/content/images/size/w600/2024/01/port-mapping-docker-twc_1.webp 600w, https://christian-schou.com/content/images/size/w1000/2024/01/port-mapping-docker-twc_1.webp 1000w, https://christian-schou.com/content/images/2024/01/port-mapping-docker-twc_1.webp 1209w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Docker Port Mapping</span></figcaption></figure><p>With that in place, let's take a closer look at how we can work even more with ports.</p><div class="kg-card kg-signup-card kg-width-regular " data-lexical-signup-form="" style="background-color: #000000; display: none;">
            
            <div class="kg-signup-card-content">
                
                <div class="kg-signup-card-text kg-align-center">
                    <h2 class="kg-signup-card-heading" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">Sign up for Tech with Christian</span></h2>
                    <p class="kg-signup-card-subheading" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">A Pragmatic Solution Architect &amp; Software Engineer sharing his knowledge with the web.</span></p>
                    
        <form class="kg-signup-card-form" data-members-form="signup">
            
            <div class="kg-signup-card-fields">
                <input class="kg-signup-card-input" id="email" data-members-email="" type="email" required="true" placeholder="Your email">
                <button class="kg-signup-card-button kg-style-accent" style="color: #FFFFFF;" type="submit">
                    <span class="kg-signup-card-button-default">Join</span>
                    <span class="kg-signup-card-button-loading"><svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
        <g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper">
            <g class="nc-loop-dots-4-24-icon-o">
                <circle cx="4" cy="12" r="3"></circle>
                <circle cx="12" cy="12" r="3"></circle>
                <circle cx="20" cy="12" r="3"></circle>
            </g>
            <style data-cap="butt">
                .nc-loop-dots-4-24-icon-o{--animation-duration:0.8s}
                .nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite}
                .nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:-.3s;animation-delay:calc(var(--animation-duration)/-2.666)}
                .nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:-.15s;animation-delay:calc(var(--animation-duration)/-5.333)}
                .nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px}
                @keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}
            </style>
        </g>
    </svg></span>
                </button>
            </div>
            <div class="kg-signup-card-success" style="color: #FFFFFF;">
                Email sent! Check your inbox to complete your signup.
            </div>
            <div class="kg-signup-card-error" style="color: #FFFFFF;" data-members-error=""></div>
        </form>
        
                    <p class="kg-signup-card-disclaimer" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">I promise zero spam. You can unsubscribe anytime.</span></p>
                </div>
            </div>
        </div><h2 id="how-to-map-multiple-ports">How To Map Multiple Ports</h2><p>With Docker, we can run multiple services inside one container and that makes the container expose multiple ports that we have to map.</p><p>In a Docker Compose file, we can easily map multiple ports for the service we are defining. To illustrate it with a YAML example, I have copied the example from before and modified it to map multiple ports at once.</p><pre><code class="language-yaml">version: '3.3'

services:
  web-app:
    image: cool-project/web-app:latest
    ports:
      - 80:8080
      - 2222:22

...</code></pre><p>In my example above the container exposes a service at port <code>8080</code> and an <strong>SSH</strong> service at port <code>22</code>. Port <code>22</code> is often used for local <strong>SSH</strong> connections on the host connecting to it. To fix that I have mapped the port to <code>2222</code> at the host machine. By default all traffic is served using the TCP protocol, so no need to specify that every time.</p><h2 id="mapping-using-port-ranges">Mapping Using Port Ranges</h2><p>If you have a lot of ports, it can get messy in the compose file if you are mapping each port one by one. Instead, we can use<strong> port ranges</strong> to specify multiple host ports in one go.</p><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">🐋</div><div class="kg-callout-text">When using port ranges, Docker will automatically assign the first available port you have specified in the list. When moving on, Docker will then continue assigning ports in an ascending order - meaning that the next available port will be used.</div></div><p>To use the port range syntax in your Docker Compose file, you can follow the following pattern.</p><pre><code class="language-text">"[host_port_start-host_port_end]container-name[/transfer protocol]"</code></pre><p>Here is an example of how it's implemented in a compose file.</p><pre><code class="language-yaml">version: '3.3'

services:
  web-app:
    image: cool-project/web-app:latest
    ports:
      - "8080-8085:80"

...</code></pre><p>What happens when we ask Docker Compose to spin up this file? 🤔</p><p>Docker will now look at the service and discover our port range. The first available port in our port range is <code>8080</code>, and this one will be assigned to port <code>80</code> in our container. 💪</p><p>If you need the opposite, then swap the port range to be inside the container instead of the host, as I have shown below.</p><pre><code class="language-yaml">version: '3.3'

services:
  web-app:
    image: cool-project/web-app:latest
    ports:
      - "80:8080-8085"

...</code></pre><h2 id="automatic-port-mapping-%F0%9F%A4%96">Automatic Port Mapping 🤖</h2><p>Manual specification of ports is great for production environments, where we would like everything to be configured and not left up to chance.</p><p>When developing an application we don't bother if the container is started on one port or another, we just want it to launch, test, debug, develop, etc...</p><p>If you do not specify a specific port in the compose file for your container/service, Docker will do it for you automatically. Docker will look for the first available port and assign it to your service by itself.</p><p>This is all you need to make it happen 😎</p><pre><code class="language-yaml">version: '3.3'

services:
  web-app:
    image: cool-project/web-app:latest
    ports:
      - "8080"

...</code></pre><p>What happens above? 🤔 When the container launches, it will listen for requests at port <code>8080</code> (<em>in the container</em>). Docker will now look for the first available port at the <strong>host</strong> and assign it to the <strong>container</strong>, making the mapping happen automatically. ✅</p><h2 id="being-very-specific-in-port-mappings">Being Very Specific In Port Mappings</h2><p>So far we have been using the short-form syntax of mapping ports in our Docker Compose file. What if we would like to be very specific about the port mapping? Well, then we can use the long-form syntax.</p><p>Just like before on the service, we use the ports section, but this time we are being very explicit in our configuration. Below is a template of the syntax for achieving this.</p><pre><code class="language-yaml">version: '3.3'

services:
  web-app:
    image: cool-project/web-app:latest
    ports:
      - target: "container-port"
        host_ip: host_ip
        published: "published-port"
        protocol: [tcp or udp]
        mode: host

...</code></pre><p>Let's see that in action for our <code>web-app</code> in the compose file. The target on the container will be <code>8080</code>, as that is the default port for .NET 8 containerized apps, and I love .NET 🥳</p><pre><code class="language-yaml">version: '3.3'

services:
  web-app:
    image: cool-project/web-app:latest
    ports:
      - target: "8080"
        host_ip: 127.0.0.1
        published: "80"
        protocol: tcp
        mode: host
      - target: "8080"
        host_ip: 127.0.0.1
        published: "443"
        protocol: tcp
        mode: host

...</code></pre><p>So what exactly happens above? 🤔 Let me explain...</p><ul><li>The <code>target</code> property is used to specify the container port or port range.</li><li>We use the <code>host_ip</code> to tell Docker what IP address we would like to bind on the host. (<em>if no <code>host_ip</code> is specified, <code>0.0.0.0</code> will be used making it available for all Docker network interfaces</em>)</li><li>The <code>published</code> property is the one we tell our host to map requests to from the client environment to the service.</li><li>As explained earlier, the <code>protocol</code> tells our network configuration how we would like to communicate over the network.</li><li>Docker has several drivers for the network mode. The default value, if none is specified, is <code>bridge</code>, but for this I have chosen <code>host</code>. Choosing <code>host</code>, allows me to remove network isolation between the container and the Docker host.</li></ul><p>You can read more about the network configuration at the link below.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.docker.com/network/?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Networking overview</div><div class="kg-bookmark-description">Learn how networking works from the container’s point of view</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://docs.docker.com/assets/favicons/docs@2x.ico" alt=""><span class="kg-bookmark-author">Docker Documentation</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.docker.com/assets/favicons/docs@2x.ico" alt="" onerror="this.style.display = 'none'"></div></a></figure><h2 id="summary">Summary</h2><p>In this post you learned about Docker port mapping. You saw examples explaining how to <strong>map host ports to container ports</strong> to enable communication between the host and containerized applications. It covers key topics like <strong>basic port mapping</strong>, <strong>mapping multiple ports</strong>, and <strong>using port ranges</strong> to simplify configurations. Additionally, we also had a look at <strong>automatic port mapping</strong> and the more detailed <strong>long-form syntax</strong> for precise control over port mapping.</p><p>You should now have a clearer understanding of Docker port mapping and be able to apply this knowledge to solve your container networking issues. If you got any questions, please let me know in the comments below. Until next time - Happy Dockerizing! 🐳</p>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[URL-Safe Base64 Encoding With .NET 9]]></title>
                    <description><![CDATA[.NET 9 introduces a new Base64Url type for URL-safe Base64 encoding, solving the issue of unsafe characters like /, +, and &#x3D;.]]></description>
                    <link>https://christian-schou.com/blog/url-safe-base64-encoding-with-dotnet-9/</link>
                    <guid isPermaLink="false">67063bfad579200001357080</guid>

                        <category><![CDATA[.NET Tips]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Wed, 09 Oct 2024 04:12:00 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/10/url-sage-base-64-encoding-twc-dotnet-tips.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/10/url-sage-base-64-encoding-twc-dotnet-tips.webp" alt="URL-Safe Base64 Encoding With .NET 9"/> <p>In .NET 9 we will get a new Base64Url Type. It will make it possible for developers to do URL-safe Base64 encoding on the fly.</p><p>Coming from earlier version of .NET you might think, why? We already have the <code>Convert.ToBase64String()</code> method? That's correct, but that method can produce strings which contains characters such as: <code>/</code>, <code>+</code>,or <code>=</code>. These characters are not safe for use in URL's without encoding them first, <strong>because they are used for special operations</strong>.</p><p>To fix that, the .NET team now introduces a new <code>Base64Url.EncodeToString()</code> method. 👌</p><h2 id="what-is-base64-and-why-%F0%9F%A4%94">What Is Base64 And Why? 🤔</h2><div class="kg-card kg-callout-card kg-callout-card-purple"><div class="kg-callout-emoji">💡</div><div class="kg-callout-text">Base64 is an encoding scheme that translates arbitrary bytes into text composed of a specific set of 64 characters. It's a common approach for transferring data and has long been supported via a variety of methods.</div></div><div class="kg-card kg-signup-card kg-width-regular " data-lexical-signup-form="" style="background-color: #000000; display: none;">
            
            <div class="kg-signup-card-content">
                
                <div class="kg-signup-card-text kg-align-center">
                    <h2 class="kg-signup-card-heading" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">Sign up for Tech with Christian</span></h2>
                    <p class="kg-signup-card-subheading" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">A Pragmatic Solution Architect &amp; Software Engineer sharing his knowledge with the internet.</span></p>
                    
        <form class="kg-signup-card-form" data-members-form="signup">
            
            <div class="kg-signup-card-fields">
                <input class="kg-signup-card-input" id="email" data-members-email="" type="email" required="true" placeholder="Your email">
                <button class="kg-signup-card-button kg-style-accent" style="color: #FFFFFF;" type="submit">
                    <span class="kg-signup-card-button-default">Subscribe</span>
                    <span class="kg-signup-card-button-loading"><svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
        <g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper">
            <g class="nc-loop-dots-4-24-icon-o">
                <circle cx="4" cy="12" r="3"></circle>
                <circle cx="12" cy="12" r="3"></circle>
                <circle cx="20" cy="12" r="3"></circle>
            </g>
            <style data-cap="butt">
                .nc-loop-dots-4-24-icon-o{--animation-duration:0.8s}
                .nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite}
                .nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:-.3s;animation-delay:calc(var(--animation-duration)/-2.666)}
                .nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:-.15s;animation-delay:calc(var(--animation-duration)/-5.333)}
                .nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px}
                @keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}
            </style>
        </g>
    </svg></span>
                </button>
            </div>
            <div class="kg-signup-card-success" style="color: #FFFFFF;">
                Email sent! Check your inbox to complete your signup.
            </div>
            <div class="kg-signup-card-error" style="color: #FFFFFF;" data-members-error=""></div>
        </form>
        
                    <p class="kg-signup-card-disclaimer" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">No spam. Unsubscribe anytime. Totally free!</span></p>
                </div>
            </div>
        </div><h2 id="how-to-use-it-%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB">How To Use It 🧑‍💻</h2><p>Here us a practical example of how to implement and use the <strong>Base64URL</strong> helper in <strong>.NET 9</strong>.</p><pre><code class="language-csharp">byte[] stringToEncode = Encoding.UTF8.GetBytes("Tech with Christian");

var unsafeEncoding = Convert.ToBase64String(stringToEncode);
var safeEncoding = Base64Url.EncodeToString(stringToEncode);

Console.WriteLine($"Unsafe encoding: {unsafeEncoding}");
Console.WriteLine($"Safe encoding: {safeEncoding}");

// Unsafe encoding: VGVjaCB3aXRoIENocmlzdGlhbg==
// Safe encoding: VGVjaCB3aXRoIENocmlzdGlhbg</code></pre><p>That's how easy it is to use the new <code>Base64Url</code> class with the <code>EncodeToString</code> method. <strong>Just remember it's only available beginning from </strong><a href="https://dotnet.microsoft.com/en-us/download/dotnet/9.0?ref=christian-schou.com" rel="noreferrer"><strong>.NET 9</strong></a><strong>. </strong></p><h2 id="resources-%F0%9F%93%9A">Resources 📚</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/api/system.buffers.text.base64url?view=net-9.0&ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Base64Url Class (System.Buffers.Text)</div><div class="kg-bookmark-description">Converts between binary data and URL-safe ASCII encoded text that’s represented in Base64Url characters.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-1.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">dotnet-bot</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/open-graph-image.png" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/libraries?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">What’s new in .NET libraries for .NET 9</div><div class="kg-bookmark-description">Learn about the new .NET libraries features introduced in .NET 9.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon-2.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">gewarren</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/dotnet-logo.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[A Docker Clean Up Cheat Sheet]]></title>
                    <description><![CDATA[Need to quickly clean up your Docker environment? Here&#x27;s a simple list of Linux and PowerShell commands to remove old images, volumes, and containers. Just copy, run, and you&#x27;re good to go 🚀]]></description>
                    <link>https://christian-schou.com/blog/docker-clean-up-cheat-sheet/</link>
                    <guid isPermaLink="false">6703743ff979b50001c3da24</guid>

                        <category><![CDATA[Docker]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Mon, 07 Oct 2024 05:37:00 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/10/docker-clean-up-cheat-sheet-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/10/docker-clean-up-cheat-sheet-twc.webp" alt="A Docker Clean Up Cheat Sheet"/> <p>Are you looking for an easy way to clean up your Docker environment? I recently came across several threads on another website where users were asking how to clean up their Docker servers.</p><p>This included everything from images and volumes to containers with volumes, or even the entire environment. It's fairly easy, but since it's not something we do every day, most people would need to look it up each time.</p><p>Below is a quick command to help you clean up different components. Just copy the command and run it on your system. Bon appétit! 🤌</p><h2 id="linux-%F0%9F%90%A7">Linux 🐧</h2><p>The commands you will find below is for Linux only. If you are looking for Windows commands (<em>PowerShell</em>) please go here. Windows Commands.</p><h3 id="delete-all-containers-including-their-volume-%F0%9F%9A%A7">Delete All Containers Including Their Volume 🚧</h3><p>To delete all containers and their volumes on your system, you can use the below command.</p><pre><code class="language-bash">docker rm -vf $(docker ps -aq)</code></pre><h3 id="delete-all-images-%F0%9F%93%A6">Delete All Images 📦</h3><p>To delete all images on your system, you can use the command below.</p><pre><code class="language-bash">docker rmi -f $(docker images -aq)</code></pre><h3 id="delete-containers-in-exited-state-%F0%9F%8F%9A%EF%B8%8F">Delete Containers In Exited State 🏚️</h3><pre><code class="language-bash">docker rm $(docker ps -a -f status=exited -q)</code></pre><h3 id="delete-containers-in-created-state-%F0%9F%8F%A0">Delete Containers In Created State 🏠</h3><pre><code class="language-bash">docker rm $(docker ps -a -f status=created -q)</code></pre><h3 id="delete-everything-%E2%98%A0%EF%B8%8F">Delete Everything ☠️</h3><div class="kg-card kg-callout-card kg-callout-card-yellow"><div class="kg-callout-emoji">🤓</div><div class="kg-callout-text">If you are looking for a way to prune your whole system, you can use the command below. Please be aware that it will remove <b><strong style="white-space: pre-wrap;">EVERYTHING NOT IN USE</strong></b>! It's a great way to free up diskspace if you are running low. </div></div><pre><code class="language-bash">docker system prune -a --volumes</code></pre><p>Running it will result in the following output in your terminal.</p><pre><code class="language-text">WARNING! This will remove:
    - all stopped containers
    - all networks not used by at least one container
    - all volumes not used by at least one container
    - all images without at least one container associated to them
    - all build cache</code></pre><h2 id="windows-powershell-%F0%9F%AA%9F">Windows (PowerShell) 🪟</h2><p>PowerShell treats <code>$()</code> as a subexpression operator, just like in the shell, but it needs to be used differently in some cases. Due to that, we can try using <code>ForEach-Object</code> for more robustness when running the command.</p><h3 id="delete-all-containers-including-their-volume-%F0%9F%9A%A7-1">Delete All Containers Including Their Volume 🚧</h3><p>To delete all containers and their volumes on your system, you can use the below PowerShell command.</p><pre><code class="language-PowerShell">docker ps -aq | ForEach-Object { docker rm -vf $_ }</code></pre><h3 id="delete-all-images-%F0%9F%93%A6-1">Delete All Images 📦</h3><p>To delete all images on your system, you can use the PowerShell command below.</p><pre><code class="language-powershell">docker images -aq | ForEach-Object { docker rmi -f $_ }</code></pre><h3 id="delete-containers-in-exited-state-%F0%9F%8F%9A%EF%B8%8F-1">Delete Containers In Exited State 🏚️</h3><p>Looking for a way to delete all the containers in an <strong>exited</strong> state? Remember to manually remove the volumes after, if any is attached.</p><pre><code class="language-bash">docker ps -a -f status=exited -q | ForEach-Object { docker rm $_ }
</code></pre><h3 id="delete-containers-in-created-state-%F0%9F%8F%A0-1">Delete Containers In Created State 🏠</h3><p>Looking for a way to delete all the containers in an <strong>created</strong> state? Remember to manually remove the volumes after, if any is attached.</p><pre><code class="language-bash">docker ps -a -f status=created -q | ForEach-Object { docker rm $_ }
</code></pre><h3 id="delete-everything-%E2%98%A0%EF%B8%8F-1">Delete Everything ☠️</h3><p>This command is the same across Windows and Linux. It will give you the same warning as it does on Linux.</p><pre><code class="language-powershell">docker system prune -a --volumes</code></pre><pre><code class="language-text">WARNING! This will remove:
    - all stopped containers
    - all networks not used by at least one container
    - all volumes not used by at least one container
    - all images without at least one container associated to them
    - all build cache</code></pre><h2 id="summary">Summary</h2><p>I hope you got your problem solved and learned something at the same time.</p><h2 id="resources-%F0%9F%93%9A">Resources 📚</h2><p>Below are links for reference to the locations with documentation I have used to create this Docker clean up tutorial.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.docker.com/reference/cli/docker/system/prune/?ref=christian-schou.com#extended-description"><div class="kg-bookmark-content"><div class="kg-bookmark-title">docker system prune</div><div class="kg-bookmark-description">″”</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/docs@2x.ico" alt=""><span class="kg-bookmark-author">Docker Documentation</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/thumbnail.webp" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Replace IF-Statements With Null Conditional Operators]]></title>
                    <description><![CDATA[Ever heard of the null conditional operator (?.) in C#? It helps make your code cleaner by safely accessing members or methods that might be null, returning null instead of throwing exceptions. A handy feature from C# 6.0! ✌️]]></description>
                    <link>https://christian-schou.com/blog/replace-if-statements-with-null-conditional-operators/</link>
                    <guid isPermaLink="false">66a79f5693491200012a7fc7</guid>

                        <category><![CDATA[.NET Tips]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Sun, 29 Sep 2024 09:41:00 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/10/replace-if-statements-with-null-conditional-operators-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/10/replace-if-statements-with-null-conditional-operators-twc.webp" alt="Replace IF-Statements With Null Conditional Operators"/> <p>Ever heard about the <strong>null conditional operator</strong>? It is also referenced by instructors, software engineers and programmers in general as:</p><ul><li><strong>Null propagation operator</strong>.</li><li><strong>Safe navigation operator</strong>.</li></ul><p>It was a feature we first saw in <strong>C# 6.0 </strong>and <strong>I personally use it a lot</strong>! ✌️ It will allow you to make your code much more clean and concise, when you are working with data that might be null.</p><h2 id="how-does-it-work-%F0%9F%A4%94">How Does It Work? 🤔</h2><p>The null conditional operators is also known as a <code>?.</code> in C# (<em>question mark with a. dot</em>). This notation is used to access a member or invoke a method that you know might return null when invoked. If the invoked object is <strong>null</strong>, it will return null instead of throwing a new exception, genius right?! 🔥</p><h2 id="how-to-do-it-%F0%9F%92%A1">How To Do It? 💡</h2><p>Let's get to the fun part and see how we can replace an IF-statement with this null conditional operator, in practice.</p><div class="kg-card kg-callout-card kg-callout-card-red"><div class="kg-callout-emoji">👎</div><div class="kg-callout-text"><b><strong style="white-space: pre-wrap;">The long way with an IF-statement.</strong></b></div></div><pre><code class="language-csharp">if (invoice != null &amp;&amp; invoice.Payment != null)
{
    invoice.Payment.AddFee(10);
}</code></pre><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">👍</div><div class="kg-callout-text"><b><strong style="white-space: pre-wrap;">Using null conditional operator instead.</strong></b></div></div><pre><code class="language-csharp">invoice?.Payment?.AddFee(10);
</code></pre><p>See the difference? 😅</p><h2 id="why-should-i-do-it-%F0%9F%A4%93">Why Should I Do It? 🤓</h2><p>I know a lot of programmers would think, yeah but I know my IF-statements and they work, why should I make the switch? Here are three reasons:</p><ol><li><strong>Cleaner Code</strong> - You will reduce the need for if-statements, making your code shorter and easier to read. 🫶</li><li><strong>Prevents Errors</strong> - You will avoid the <code>NullReferenceException</code> by safely handling null objects. What's not to like? ✌️</li><li><strong>Consistent Null Checks</strong> - Standardizes null handling, improving code reliability and maintainability. Other engineers will love you! ❤️</li></ol><h2 id="resources-%F0%9F%94%97">Resources 🔗</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators?ref=christian-schou.com#null-conditional-operators--and-"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Member access and null-conditional operators and expressions: - C# reference</div><div class="kg-bookmark-description">C# operators that you use to access type members or null-conditionally access type members. These operators include the dot operator - `.`, indexers - `[`, `]`, `^` and `..`, and invocation - `(`, `)`.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://learn.microsoft.com/favicon.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">pkulikov</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://learn.microsoft.com/dotnet/media/logo_csharp.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Do You Know About The File Access Modifier In C#?]]></title>
                    <description><![CDATA[The file access modifier in C# is like keeping your tools in the right drawer—everything stays organized, and nothing gets mixed up where it doesn’t belong!]]></description>
                    <link>https://christian-schou.com/blog/the-file-access-modifier-in-csharp/</link>
                    <guid isPermaLink="false">66ea7969f979b50001c3d993</guid>

                        <category><![CDATA[.NET Tips]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Wed, 18 Sep 2024 06:17:00 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/10/the-file-access-modifier-in-csharp-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/10/the-file-access-modifier-in-csharp-twc.webp" alt="Do You Know About The File Access Modifier In C#?"/> <p>In C# 11, a new access modifier called <code>file</code> was introduced. It restricts the <strong>accessibility</strong> of <strong>types</strong> and <strong>members</strong> to the containing file, offering a lightweight alternative to <code>internal</code> or <code>private</code>. 🤘</p><p>This is especially useful for helper classes or methods you want to limit to a single file, ensuring cleaner code organization and better encapsulation. 🔥</p><h2 id="a-quick-demonstration-%F0%9F%A7%91%E2%80%8D%F0%9F%92%BB">A Quick Demonstration 🧑‍💻</h2><p>Below is a simple example on how to use the <code>file</code> access modifier in C#.</p><pre><code class="language-csharp">file class HelperClass
{
    public static string GetMessage()
    {
        return "This is a file-scoped class.";
    }
}

public class MyClass
{
    public string DisplayMessage()
    {
        return HelperClass.GetMessage();
    }
}
</code></pre><p><strong>What is going on above? Let me explain 😄</strong></p><p>The <code>HelperClass</code> is restricted to the file <code>MyHelper.cs</code> because of the <code>file</code> access modifier. It can only be used within this file, ensuring it’s not accessible anywhere else in the project.</p><p>This keeps <code>HelperClass</code> isolated, allowing you to organize utility or helper functionality without exposing it to other parts of your application. Awesome right?! 👌</p><h2 id="still-not-getting-it-%F0%9F%98%85">Still Not Getting It? 😅</h2><p>I totally understand! Let me try with another example. Imagine you have two files with helper classes that could potentially conflict if they were accessible across the entire project.</p><h3 id="without-file-modifier">Without <code>file</code> Modifier</h3><p>Image you got a file named <code>Utilities1.cs</code>.</p><pre><code class="language-csharp">internal class Logger
{
    public static void Log(string message)
    {
        Console.WriteLine($"Utilities1 Log: {message}");
    }
}

public class MyClass1
{
    public void DoWork()
    {
        Logger.Log("Doing work in MyClass1.");
    }
}
</code></pre><p>And another named <code>Utilities2.cs</code>.</p><pre><code class="language-csharp">internal class Logger
{
    public static void Log(string message)
    {
        Console.WriteLine($"Utilities2 Log: {message}");
    }
}

public class MyClass2
{
    public void DoWork()
    {
        Logger.Log("Doing work in MyClass2.");
    }
}
</code></pre><p>What could possible end up bad here? Well... both <code>Logger</code> classes are <strong>internal</strong>. If someone from another file in the same project tries to use <code>Logger</code>, there will be confusion or conflicts since both files have a <code>Logger</code> class. You might even accidentally reference the wrong one. 😬</p><p>So how can we solve that?</p><h3 id="with-file-modifier">With <code>file</code> Modifier</h3><p>Let's update our <strong>Utilities</strong> to use the <code>file</code> access modifier instead of <code>internal</code>.</p><p>First file named <code>Utilities1.cs</code> goes here.</p><pre><code class="language-csharp">file class Logger
{
    public static void Log(string message)
    {
        Console.WriteLine($"Utilities1 Log: {message}");
    }
}

public class MyClass1
{
    public void DoWork()
    {
        Logger.Log("Doing work in MyClass1.");
    }
}
</code></pre><p>And the second one named <code>Utilities2.cs</code> is here.</p><pre><code class="language-csharp">file class Logger
{
    public static void Log(string message)
    {
        Console.WriteLine($"Utilities2 Log: {message}");
    }
}

public class MyClass2
{
    public void DoWork()
    {
        Logger.Log("Doing work in MyClass2.");
    }
}
</code></pre><p><strong>Why This Is a Good Idea? 🤨</strong></p><ul><li><strong>No Naming Conflicts! </strong>Each file has its own <code>Logger</code> class, but they can't conflict because their scope is limited to the file. Both <code>Logger</code> classes can coexist even though they have the same name, and neither will accidentally be accessed from other files.</li><li><strong>Cleaner Encapsulation.</strong> You ensure that <code>Logger</code> in <code>Utilities1.cs</code> and <code>Utilities2.cs</code> only impacts the respective file. If you don't need the <code>Logger</code> class outside these files, <code>file</code> helps prevent accidental exposure of them.</li></ul><p>This approach helps with modularity, avoids naming clashes, and keeps the scope of helper classes or utility methods limited to where they're needed.</p><h3 id="an-analogy-to-understand-it">An Analogy To Understand It</h3><p>Imagine you're working on two different school projects at the same time, and each project has its own helper friend named "Alex." But the problem is, if both projects are in the same room, you might get confused about which "Alex" to ask for help!</p><p>Now, the <strong>file</strong> access modifier is like keeping each "Alex" in their own room (<em>one for each project</em>). So, when you're working on <strong>Project 1</strong>, you only see and talk to <strong>Alex from Project 1</strong>, and when you’re working on <strong>Project 2</strong>, you only see <strong>Alex from Project 2</strong>.</p><p>This way, there’s no confusion or mix-up, and you can work on both projects without accidentally calling the wrong "Alex". Genius right? 🙌</p><h2 id="resources">Resources</h2><p>You can read more about the file keyword from the <a href="https://learn.microsoft.com/en-us/dotnet/csharp/?ref=christian-schou.com" rel="noreferrer">official Microsoft documentation</a> below.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/file?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">file keyword - C# reference</div><div class="kg-bookmark-description">file modifier: Declare types whose scope is the file in which it’s declared</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://christian-schou.com/content/images/icon/favicon.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">BillWagner</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://christian-schou.com/content/images/thumbnail/logo_csharp.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>]]></content:encoded>
                </item>
                <item>
                    <title><![CDATA[Why You Shouldn&#x27;t Concatenate Strings Using &quot;+&quot; In Loops]]></title>
                    <description><![CDATA[Strings should not be concatenated using &quot;+&quot; in a loop. Learn what to do instead in this .NET tip. 💡]]></description>
                    <link>https://christian-schou.com/blog/why-you-shouldnt-concatenate-strings-using-in-plus-loops/</link>
                    <guid isPermaLink="false">669dfd1093491200012a7e92</guid>

                        <category><![CDATA[.NET Tips]]></category>

                        <dc:creator><![CDATA[Christian Schou Køster]]></dc:creator>

                    <pubDate>Mon, 22 Jul 2024 07:47:00 +0200</pubDate>

                        <media:content url="https://christian-schou.com/content/images/2024/07/dont-concat-strings-using-plus-operator-twc.webp" medium="image"/>

                    <content:encoded><![CDATA[<img src="https://christian-schou.com/content/images/2024/07/dont-concat-strings-using-plus-operator-twc.webp" alt="Why You Shouldn&#x27;t Concatenate Strings Using &quot;+&quot; In Loops"/> <p>In this <a href="https://christian-schou.com/tag/dotnet-tips/" rel="noreferrer">.NET tip</a> I will show you why strings should not be concatenated using a <code>+</code> in your loops.</p><p>I often see beginners use that operator, because it's easy and that is what they learned during the YouTube videos.. However it's not efficient and leads to bad performance. 😥</p><h2 id="why-%F0%9F%A4%B7%E2%80%8D%E2%99%82%EF%B8%8F">Why? 🤷‍♂️</h2><p>Strings are immutable, which means that once a string object is created, it cannot be modified. When you concatenate strings using the <code>+</code> operator in your loops, you actually creates a new string object at each iteration.</p><p>The old/previous objects are discarded of course, but it can lead to performance issues, turning your app into a snail 🐌. It can especially be a problem in large applications where you need to handle a lot of data.</p><h2 id="the-right-way-%E2%9C%85">The Right Way ✅</h2><p>It's not rocket science and you will gain in performance 🚀 Ever heard about the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder?ref=christian-schou.com" rel="noreferrer">StringBuilder</a>? The <code>StringBuilder</code> is designed for building strings (<em>hence it's name</em> 😄) in loops. It will allow you to append a strings, without having to create a new object for each iteration!</p><div class="kg-card kg-callout-card kg-callout-card-red"><div class="kg-callout-emoji">❌</div><div class="kg-callout-text"><b><strong style="white-space: pre-wrap;">Don't do this</strong></b></div></div><pre><code class="language-csharp">string names = string.Empty;

foreach (var name in arrayOfNames)
{
    names += name;
}
</code></pre><div class="kg-card kg-callout-card kg-callout-card-green"><div class="kg-callout-emoji">✅</div><div class="kg-callout-text"><b><strong style="white-space: pre-wrap;">Do this</strong></b></div></div><pre><code class="language-csharp">var sb = new StringBuilder();

foreach(var name in arrayOfNames)
{
    sb.Append(name);
}

var names = builder.ToString();</code></pre><div class="kg-card kg-signup-card kg-width-wide kg-style-accent" data-lexical-signup-form="" style="; display: none;">
            
            <div class="kg-signup-card-content">
                
                <div class="kg-signup-card-text kg-align-center">
                    <h2 class="kg-signup-card-heading" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">Sign up for Tech with Christian</span></h2>
                    <p class="kg-signup-card-subheading" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">A Pragmatic Software &amp; DevOps Engineer sharing his knowledge with the internet.</span></p>
                    
        <form class="kg-signup-card-form" data-members-form="signup">
            
            <div class="kg-signup-card-fields">
                <input class="kg-signup-card-input" id="email" data-members-email="" type="email" required="true" placeholder="Your email">
                <button class="kg-signup-card-button " style="background-color: #000000;color: #FFFFFF;" type="submit">
                    <span class="kg-signup-card-button-default">Subscribe</span>
                    <span class="kg-signup-card-button-loading"><svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
        <g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper">
            <g class="nc-loop-dots-4-24-icon-o">
                <circle cx="4" cy="12" r="3"></circle>
                <circle cx="12" cy="12" r="3"></circle>
                <circle cx="20" cy="12" r="3"></circle>
            </g>
            <style data-cap="butt">
                .nc-loop-dots-4-24-icon-o{--animation-duration:0.8s}
                .nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite}
                .nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:-.3s;animation-delay:calc(var(--animation-duration)/-2.666)}
                .nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:-.15s;animation-delay:calc(var(--animation-duration)/-5.333)}
                .nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px}
                @keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}
            </style>
        </g>
    </svg></span>
                </button>
            </div>
            <div class="kg-signup-card-success" style="color: #FFFFFF;">
                Email sent! Check your inbox to complete your signup.
            </div>
            <div class="kg-signup-card-error" style="color: #FFFFFF;" data-members-error=""></div>
        </form>
        
                    <p class="kg-signup-card-disclaimer" style="color: #FFFFFF;"><span style="white-space: pre-wrap;">I promise ZERO spam! You can unsubscribe anytime and it's totally free!</span></p>
                </div>
            </div>
        </div><h2 id="conclusion">Conclusion</h2><p>By using the string builder we can deal with large datasets for strings in .NET without having to sacrifice performance. It's great in conditions where you have an unknown number of strings that needs concatenation.</p><p>It will drastically reduce the memory allocations and help you improve the performance of the code in your methods.</p><h2 id="resources">Resources</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder?ref=christian-schou.com"><div class="kg-bookmark-content"><div class="kg-bookmark-title">StringBuilder Class (System.Text)</div><div class="kg-bookmark-description">Represents a mutable string of characters. This class cannot be inherited.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://learn.microsoft.com/favicon.ico" alt=""><span class="kg-bookmark-author">Microsoft Learn</span><span class="kg-bookmark-publisher">dotnet-bot</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://learn.microsoft.com/en-us/media/open-graph-image.png" alt="" onerror="this.style.display = 'none'"></div></a></figure>
<!--kg-card-begin: html-->
<script src="https://giscus.app/client.js"
        data-repo="Christian-Schou/Tech-with-Christian"
        data-repo-id="R_kgDOM27epQ"
        data-category="Announcements"
        data-category-id="DIC_kwDOM27epc4Cixjl"
        data-mapping="pathname"
        data-strict="0"
        data-reactions-enabled="1"
        data-emit-metadata="1"
        data-input-position="top"
        data-theme="light"
        data-lang="en"
        data-loading="lazy"
        crossorigin="anonymous"
        async>
</script>
<!--kg-card-end: html-->
]]></content:encoded>
                </item>
    </channel>
</rss>