<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anton B</title>
    <description>The latest articles on DEV Community by Anton B (@__3545da69e7b38a555).</description>
    <link>https://dev.to/__3545da69e7b38a555</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3759654%2F86b527b9-dc68-44fe-9bd3-d050fe957eab.png</url>
      <title>DEV Community: Anton B</title>
      <link>https://dev.to/__3545da69e7b38a555</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/__3545da69e7b38a555"/>
    <language>en</language>
    <item>
      <title>Implementing an In-Process Actor Model in .NET via System.Threading.Channels and Events</title>
      <dc:creator>Anton B</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:12:17 +0000</pubDate>
      <link>https://dev.to/__3545da69e7b38a555/implementing-an-in-process-actor-model-in-net-via-systemthreadingchannels-and-events-45mh</link>
      <guid>https://dev.to/__3545da69e7b38a555/implementing-an-in-process-actor-model-in-net-via-systemthreadingchannels-and-events-45mh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Workflow Isolation: Building In-Process Actors in .NET Without Frameworks&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/abaula/WorkflowIsolation" rel="noopener noreferrer"&gt;Example code for the article&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;In the Go world, the CSP (Communicating Sequential Processes) concept and goroutines communicating via channels have become the de facto standard for building scalable systems. In the .NET ecosystem, developers more often rely on classic async/await, which, while solving the problem of CPU thread blocking, imposes hidden architectural limitations: the code of the called class is often executed in the context of the caller's thread.&lt;/p&gt;

&lt;p&gt;In this article, we will explore how to elegantly port the philosophy of Go channels to C# classes without using heavy third-party frameworks like Akka.NET. We will break down the "Isolated Actor" pattern, which combines lightweight built-in &lt;code&gt;System.Threading.Channels&lt;/code&gt; queues with the standard C# event mechanism. This approach allows us to implement asynchronous message passing within a single process, ensuring strict adherence to the principles of Workflow Isolation and Workflow Single Responsibility at the level of individual system components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction: Rethinking Multithreading in .NET
&lt;/h2&gt;

&lt;p&gt;Are you familiar with the situation: you open the logs of a high-load service and see how the business logic of a single class is executed across five different Thread Pool threads, replacing each other? Classic async/await blurs the execution context. When ActorA calls a method of ActorB, the receiver's code unceremoniously "hijacks" the current pool thread for its own needs. As a result, the mental model of predictable execution is violated, and debugging turns into a detective story.&lt;/p&gt;

&lt;p&gt;The famous manifesto by the creators of the Go language states: "Do not communicate by sharing memory; instead, share memory by communicating." But what if we apply this principle within the C# ecosystem?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important Architectural Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The terms in this article are based on the concept of Workflow Isolation. Since in modern .NET we operate with Task abstractions, under the hood the scheduler (TaskScheduler) can switch physical OS threads on every asynchronous transition (await). We focus specifically on the isolation of the &lt;em&gt;workflow&lt;/em&gt;: the workflow (an abstraction) allocated for processing the tasks of a specific actor will never be used to execute code of classes outside the actor's area of responsibility.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using the modern .NET stack, we can design an architecture where each class exclusively owns its own isolated workflow, and interaction between them is built on asynchronous FIFO buffers. After reading this article, you will learn how to create autonomous in-process actors that exchange messages via Go-style events, maintaining strict ordering and purity of the execution pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Illusion of Isolation in Async C# and the "Thread Hijacking" Problem
&lt;/h2&gt;

&lt;p&gt;Most .NET developers are used to perceiving the async/await keywords as synonymous with safe multithreading. We write asynchronous code to avoid blocking threads, and it intuitively seems that since methods execute asynchronously, the system components themselves operate independently of each other.&lt;/p&gt;

&lt;p&gt;However, this is an illusion. Architecturally, async/await solves the problem of efficient CPU resource utilization, but it does not solve the problem of execution context isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: How Standard async/await "Hijacks" Context
&lt;/h2&gt;

&lt;p&gt;Consider a classic situation. We have two services: OrderService (processes orders) and NotificationService (sends notifications). They are written according to standard canons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;NotificationService&lt;/span&gt; &lt;span class="n"&gt;_notificationService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CreateOrderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Order creation logic (executed in Thread No. 1)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;SaveToDbAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Calling a method of another service&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_notificationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendEmailAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Order created"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens at the OS and Thread Pool level when SendEmailAsync is called?&lt;/p&gt;

&lt;p&gt;Since calling an asynchronous method in C# involves unfolding a state machine, Thread No. 1, which was just processing the order logic inside OrderService, freely enters the SendEmailAsync method and starts executing the notification logic.&lt;/p&gt;

&lt;p&gt;If a real asynchronous wait occurs inside SendEmailAsync (e.g., a network request to an SMTP server), Thread No. 1 is released. But when the network response returns, the continuation of the SendEmailAsync method is picked up by any random thread from the Thread Pool (let's say Thread No. 2). And it is this Thread No. 2 that, having finished sending the email, will return back to OrderService and continue executing the remaining code there.&lt;/p&gt;

&lt;p&gt;From a hardware perspective, this is efficient. From an architectural perspective, it's chaos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Violation of responsibility boundaries: The code of an external service (NotificationService), which is far outside the boundaries of OrderService's responsibility, is executed within the current order creation workflow.&lt;/li&gt;
&lt;li&gt;Implicit delays and blockages: Instead of doing its direct job, the OrderService workflow is forced to idle and wait for the complete completion of someone else's computations. The component completely loses control over which specific tasks are currently consuming the resource allocated to its workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: Architectural Shift to Workflow Isolation and Workflow Single Responsibility
&lt;/h2&gt;

&lt;p&gt;To bring order to complex distributed systems within a single process, we need to rethink object interaction. Instead of direct method calls, we transition to the concept of Isolated Actors.&lt;/p&gt;

&lt;p&gt;We introduce two key rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Workflow Isolation: The workflow allocated for processing the tasks of a specific actor will never be used to execute code of classes outside the actor's area of responsibility.&lt;/li&gt;
&lt;li&gt;Workflow Single Responsibility: The allocated workflow of an actor has the right to execute only the business logic for which this specific actor is responsible.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two actors no longer call each other's methods directly. They throw data structures into channels. Our actor no longer exposes heavy methods like SendEmailAsync. Instead, it has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An incoming buffer (Mailbox): A fast, thread-safe FIFO queue based on System.Threading.Channels.&lt;/li&gt;
&lt;li&gt;Its own worker: An infinite loop launched in Task.Run that sequentially reads this buffer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When OrderService wants to send a notification, it no longer executes the sending code. It simply calls the instantaneous, non-blocking SendAsync() method into the NotificationService buffer (equivalent to the &lt;code&gt;ch &amp;lt;- message&lt;/code&gt; operation in Go) and immediately goes back to its own business. The order workflow is completely isolated from foreign contexts, and the ordering of messages is guaranteed by the channel structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of an "Isolated Actor" Based on System.Threading.Channels
&lt;/h2&gt;

&lt;p&gt;Let's move on to the practical implementation. To build an efficient in-process message passing system, a standard &lt;code&gt;ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code&gt; won't do—you'd have to build complex locking and signaling logic around it using &lt;code&gt;SemaphoreSlim&lt;/code&gt; or &lt;code&gt;TaskCompletionSource&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In modern .NET, the &lt;code&gt;System.Threading.Channels&lt;/code&gt; library is perfectly suited for these purposes. It is an ultra-fast, thread-safe data structure implementing the "Producer-Consumer" pattern with near-zero memory allocation.&lt;/p&gt;

&lt;p&gt;Below is the complete code for our SequentialActor. It strictly preserves ordering, isolates the workflow, and correctly releases resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Channels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Event argument class for passing data between actors&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActorMessageEventArgs&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Payload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;SenderName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ActorMessageEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;senderName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Payload&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;SenderName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;senderName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SequentialActor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IAsyncDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;CancellationTokenSource&lt;/span&gt; &lt;span class="n"&gt;_cts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_processingTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_isDisposed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Feedback event for other actors&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActorMessageEventArgs&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;MessageProcessed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SequentialActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_cts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CancellationTokenSource&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Configuring channel optimization for the actor architecture&lt;/span&gt;
        &lt;span class="n"&gt;_buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateUnbounded&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;UnboundedChannelOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// FIFO GUARANTEE: We explicitly specify that only ONE worker will read from the buffer.&lt;/span&gt;
            &lt;span class="c1"&gt;// This disables internal read contention and guarantees strict ordering.&lt;/span&gt;
            &lt;span class="n"&gt;SingleReader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

            &lt;span class="c1"&gt;// Disallow synchronous continuations so that the calling workflow thread does not get&lt;/span&gt;
            &lt;span class="c1"&gt;// pulled into the pipeline processing logic when the buffer is filled/freed.&lt;/span&gt;
            &lt;span class="n"&gt;AllowSynchronousContinuations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Non-blocking method to write a message to the actor's buffer.&lt;/span&gt;
    &lt;span class="c1"&gt;/// Can be safely called concurrently from any other threads.&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ObjectDisposedException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isDisposed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Starts the actor's lifecycle. Creates an isolated workflow.&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;StartProcessing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ObjectDisposedException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isDisposed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_processingTask&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;_processingTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// The ReadAllAsync method returns an async stream (IAsyncEnumerable).&lt;/span&gt;
                &lt;span class="c1"&gt;// The loop will extract messages strictly in FIFO order.&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// Workflow Isolation point: processing code is executed STRICTLY sequentially.&lt;/span&gt;
                    &lt;span class="c1"&gt;// The actor's workflow performs exclusively its internal tasks,&lt;/span&gt;
                    &lt;span class="c1"&gt;// adhering to Workflow Single Responsibility. The next message will not start&lt;/span&gt;
                    &lt;span class="c1"&gt;// processing until the current await completes.&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessMessageInternalAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                    &lt;span class="c1"&gt;// Notify subscribers via event&lt;/span&gt;
                    &lt;span class="nf"&gt;OnMessageProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ActorMessageEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OperationCanceledException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Standard cancellation via CancellationToken&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Business logic errors should not crash the entire application lifecycle&lt;/span&gt;
                &lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ProcessMessageInternalAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Simulating useful class work (DB operations, HTTP requests, etc.)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_cts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnMessageProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActorMessageEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;MessageProcessed&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"[&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Error: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="n"&gt;Resource&lt;/span&gt; &lt;span class="n"&gt;Cleanup&lt;/span&gt; &lt;span class="n"&gt;Interfaces&lt;/span&gt; &lt;span class="nf"&gt;Implementation&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Graceful&lt;/span&gt; &lt;span class="n"&gt;Shutdown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isDisposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_isDisposed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Gracefully close the buffer for writing.&lt;/span&gt;
        &lt;span class="c1"&gt;// New messages are not accepted, but the ReadAllAsync loop will continue working&lt;/span&gt;
        &lt;span class="c1"&gt;// until it processes ALL messages already in the queue.&lt;/span&gt;
        &lt;span class="n"&gt;_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryComplete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Wait for the workflow to completely finish draining the queue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_processingTask&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_processingTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;MessageProcessed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Protect against memory leaks via events&lt;/span&gt;
        &lt;span class="n"&gt;_cts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SuppressFinalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_isDisposed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_isDisposed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryComplete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_cts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// In a synchronous scenario, we have to forcefully interrupt processing&lt;/span&gt;

        &lt;span class="n"&gt;_processingTask&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;GetAwaiter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Safe blocking wait for Task&lt;/span&gt;
        &lt;span class="n"&gt;MessageProcessed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_cts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SuppressFinalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;endregion&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architectural Accents of the Code:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;SingleReader = true&lt;/code&gt; flag: Under the hood, System.Threading.Channels switches to a highly optimized data structure. It doesn't need to check if another thread is trying to grab the element simultaneously. This provides a colossal speed boost and reduces CPU load.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;AllowSynchronousContinuations = false&lt;/code&gt; parameter: If not set, the thread writing a message via SendAsync might be forcibly involved by the scheduler in executing the worker's logic itself. By setting it to false, we strictly separate contexts: the sender thread only puts data in and leaves instantly.&lt;/li&gt;
&lt;li&gt;Dual Dispose implementation: In the async world, it's important to give the system a chance to finish gracefully (Graceful Shutdown). Closing the writer via TryComplete() signals the code "finish what you managed to grab and shut down smoothly."&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Asynchronous Binding of Actors via Events Without Breaking Isolation
&lt;/h2&gt;

&lt;p&gt;When we isolated the computations of each actor within its own incoming buffer, the next architectural question arises: how to organize feedback? For example, how can NotificationService report back to OrderService that the notification was successfully sent?&lt;/p&gt;

&lt;p&gt;The most natural way for C# is to use standard events. However, the standard event mechanism in .NET works synchronously. When an actor generates an event (calls Invoke), the code of all subscriber handlers is executed in the exact same workflow thread that triggered the event.&lt;/p&gt;

&lt;p&gt;If we simply subscribe one class to the event of another and start executing heavy logic there, we will instantly destroy our entire architecture. The workflow of the first actor will "fly off" to execute the code of the second, violating both Workflow Isolation and Workflow Single Responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Fast Forward" Rule
&lt;/h2&gt;

&lt;p&gt;To prevent events from destroying isolation, we impose a strict architectural regulation on the handler code:&lt;/p&gt;

&lt;p&gt;The subscriber has no right to execute business logic inside another actor's event handler. The only thing it is allowed to do is take the data from the event arguments and instantly forward (write) it to its own incoming buffer via SendAsync().&lt;/p&gt;

&lt;p&gt;Since writing to an unbounded &lt;code&gt;System.Threading.Channels&lt;/code&gt; (CreateUnbounded) happens instantly in memory without any locks, the calling actor's workflow performs this operation in microseconds and immediately returns to its queue. Heavy processing of the event by the subscriber will only begin when its own isolated workflow gets to this message in the FIFO queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation of Inter-Actor Communication
&lt;/h2&gt;

&lt;p&gt;Let's see what the secure binding of two actors via events looks like in the main application module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Create two isolated actors&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderActor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SequentialActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OrderProcessor"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;notificationActor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SequentialActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NotificationWorker"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// SUBSCRIPTION (Observing Workflow Isolation):&lt;/span&gt;
        &lt;span class="c1"&gt;// When OrderActor finishes processing, we notify NotificationActor.&lt;/span&gt;
        &lt;span class="n"&gt;orderActor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MessageProcessed&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Note: this lambda expression is executed in the OrderActor's workflow!&lt;/span&gt;
            &lt;span class="c1"&gt;// Therefore, there should be no heavy logic here.&lt;/span&gt;
            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;taskForNotification&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Send email for order: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Instant forward to the neighbor's buffer.&lt;/span&gt;
            &lt;span class="c1"&gt;// The OrderActor's workflow is released in fractions of a microsecond.&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notificationActor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taskForNotification&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// Launch independent workflows for each actor&lt;/span&gt;
        &lt;span class="n"&gt;orderActor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartProcessing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;notificationActor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartProcessing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Send a start signal from the external context&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;orderActor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order No. 1024"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Give actors time to sequentially execute their tasks&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why is the &lt;code&gt;_ = notificationActor.SendAsync()&lt;/code&gt; call absolutely safe here?
&lt;/h2&gt;

&lt;p&gt;The Fire-and-Forget construct (via ignoring ValueTask) is usually considered an anti-pattern, but here it is justified and safe:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Specifics of the unbounded buffer: The SendAsync method writes to Channel.CreateUnbounded. Such an in-memory queue is always ready to accept an element. The buffer addition operation executes synchronously and instantly. Under the hood, WriteAsync immediately returns an already completed ValueTask (with the IsCompleted = true flag). No allocations for the async/await state machine and no context switches occur.&lt;/li&gt;
&lt;li&gt;Isolation guarantee from the neighbor: Thanks to the &lt;code&gt;AllowSynchronousContinuations = false&lt;/code&gt; flag, even if the NotificationActor was idle and waiting for data at that moment, its awakening will happen strictly in the thread pool via the TaskScheduler. It physically cannot "hijack" the OrderActor's workflow.&lt;/li&gt;
&lt;li&gt;Predictable error interception: If the neighbor's buffer is closed (Dispose is called), the WriteAsync method will throw a ChannelClosedException synchronously at the moment of the call. The error won't hang in the background but will instantly bubble up to the try-catch block of the current actor, where it will be correctly handled without risking crashing the process.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion: The Architectural Manifesto of the "Isolated Actor"
&lt;/h2&gt;

&lt;p&gt;Implementing the "Isolated Actor" pattern based on &lt;code&gt;System.Threading.Channels&lt;/code&gt; and C# Events is a powerful way to bring order to the asynchronous code of .NET applications. Porting the philosophy of Go channels to C# classes allows us to move away from the chaotic task distribution inherent in standard async/await and transition to a strictly deterministic, predictable execution model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Main Takeaways of Applying the Pattern
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Workflow Isolation in action: Each actor turns into an impregnable fortress. The workflow allocated for its tasks is completely protected from the invasion of foreign code, making logging linear and debugging trivial.&lt;/li&gt;
&lt;li&gt;Workflow Single Responsibility at the design level: System components no longer share CPU time within a single end-to-end call. An actor's workflow is completely insured against idleness and blockages: it will never hang waiting for the execution of foreign tasks outside the scope of the current component's responsibility. Instead, it instantly delegates external work via the buffer and immediately proceeds to process the next message in its queue.&lt;/li&gt;
&lt;li&gt;High performance: Message passing occurs within RAM with near-zero allocation and speeds of millions of operations per second, thanks to the low-level optimizations of the Channels library.&lt;/li&gt;
&lt;li&gt;Safe Fire-and-Forget: Binding actors via events with instant data forwarding is an absolutely reliable architectural solution, as the write operation is atomic, synchronous under the hood, and physically cannot cause deadlocks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Should You Apply This Approach?
&lt;/h2&gt;

&lt;p&gt;This pattern is ideal for systems with complex internal data processing logic within a single process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data Processing Pipelines: When data must pass through a chain of sequential stages (e.g., validation ➔ enrichment ➔ writing to DB ➔ sending a notification).&lt;/li&gt;
&lt;li&gt;Integration Gateways and Parsers: Where it is necessary to quickly read external sources and pass tasks for processing to internal services without blocking the reading thread.&lt;/li&gt;
&lt;li&gt;Local Background Services: Implementing complex scenarios inside &lt;code&gt;BackgroundService&lt;/code&gt; in ASP.NET Core without involving heavy external message queues like RabbitMQ.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Conclusion
&lt;/h2&gt;

&lt;p&gt;You don't always need heavy distributed frameworks like Akka.NET or Proto.Actor to get the benefits of the actor model. Modern .NET provides developers with concise, lightweight, and incredibly fast tools right "out of the box." The "Isolated Actor" pattern proves that adhering to strict workflow hygiene and proper separation of execution contexts allows you to write the most complex multithreaded systems while keeping the code clean, safe, and easy to understand.&lt;/p&gt;

</description>
      <category>multithreading</category>
      <category>architecture</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>AI Assistants — The Wiki-First Approach</title>
      <dc:creator>Anton B</dc:creator>
      <pubDate>Wed, 15 Apr 2026 08:00:29 +0000</pubDate>
      <link>https://dev.to/__3545da69e7b38a555/ai-assistants-the-wiki-first-approach-3k0h</link>
      <guid>https://dev.to/__3545da69e7b38a555/ai-assistants-the-wiki-first-approach-3k0h</guid>
      <description>&lt;p&gt;In modern projects, the codebase grows exponentially, and AI assistants have become an integral part of the development workflow. However, without a single source of truth, every developer (and every AI agent) is forced to learn the project from scratch, wasting context window limits, time, and resources. The &lt;strong&gt;wiki-first&lt;/strong&gt; approach solves this problem by turning documentation into a living, automatically maintained knowledge center that dictates how both humans and AI operate.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;Note:&lt;/strong&gt; All structural examples, file names (&lt;code&gt;QWEN.md&lt;/code&gt;, &lt;code&gt;.qwen/skills/&lt;/code&gt;), and workflows in this article are provided for the &lt;strong&gt;QWEN&lt;/strong&gt; ecosystem. The methodology itself is universal and can easily be adapted to any AI agent capable of working with Markdown instructions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔍 What is Wiki-First?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Wiki-first&lt;/strong&gt; is a methodology where, before any work with the code (writing features, reviewing, refactoring, making architectural decisions), the developer or AI agent &lt;strong&gt;must&lt;/strong&gt; first study the project's wiki documentation (&lt;code&gt;docs/wiki/&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Key principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Wiki = overview, Code = details&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The wiki stores condensed, structured knowledge: system architecture, accepted patterns, service dependencies, naming conventions, and DI rules. Specific method signatures, SQL queries, or test assertions are looked up directly in the code if the wiki does not cover the task.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Benefits of the Approach
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Without Wiki-First&lt;/th&gt;
&lt;th&gt;With Wiki-First&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Project Onboarding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Everyone learns the code from scratch&lt;/td&gt;
&lt;td&gt;Ready-made overview in 5 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Knowledge Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Knowledge stays in developers' heads&lt;/td&gt;
&lt;td&gt;Knowledge is centralized in the wiki, accessible to all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Onboarding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Newcomers spend days deciphering the code&lt;/td&gt;
&lt;td&gt;Newcomers read the wiki → hours instead of days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI Agent Workflow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Wastes context scanning thousands of lines&lt;/td&gt;
&lt;td&gt;Reads concise wiki pages, saving tokens and time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Documentation Freshness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Wiki falls behind the code, quickly becomes outdated&lt;/td&gt;
&lt;td&gt;Wiki is automatically updated after every change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Standard Compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developers violate patterns out of ignorance&lt;/td&gt;
&lt;td&gt;AI and humans follow unified rules from the wiki&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🛠 How to Set Up Wiki-First in Your Project
&lt;/h2&gt;

&lt;p&gt;For quick and correct setup, use the &lt;a href="https://github.com/abaula/DevInsights/blob/main/AIAssistantWikiFirst/WIKI_FIRST_TEMPLATE.en.md" rel="noopener noreferrer"&gt;WIKI_FIRST_TEMPLATE.en.md&lt;/a&gt; template. It is designed &lt;strong&gt;for execution by an AI agent&lt;/strong&gt;, which will automatically create the entire knowledge infrastructure and skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1. Prepare Parameters
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Copy &lt;code&gt;WIKI_FIRST_TEMPLATE.en.md&lt;/code&gt; to the root of your repository.&lt;/li&gt;
&lt;li&gt;Open the file and fill the &lt;code&gt;INPUT DATA&lt;/code&gt; table with real values:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;project_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WebArchiveBackend&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;project_description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Backend API for storing and indexing web pages&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tech_stack&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;C#, .NET 8, gRPC, PostgreSQL, Dapper&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docs_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docs/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wiki_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docs/wiki/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skills_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.qwen/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source_dirs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/, tests/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 2. Launch the AI Agent
&lt;/h3&gt;

&lt;p&gt;Pass the filled file to your AI assistant with the following prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Execute the instructions from &lt;code&gt;WIKI_FIRST_TEMPLATE.en.md&lt;/code&gt; strictly step-by-step. Create the wiki structure, AI skills, and the main rules file. Do not modify the existing project code. After creating the structure, perform the Initial Ingest (Step 6)."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent will automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the &lt;code&gt;docs/wiki/&lt;/code&gt; directory and three base files:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.md&lt;/code&gt; — catalog of all wiki pages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;log.md&lt;/code&gt; — change log (append-only)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Wiki Format.md&lt;/code&gt; — guide to page formatting&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Generate three specialized AI skills in &lt;code&gt;.qwen/skills/&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wiki-workflow&lt;/code&gt; — knowledge management (ingest, query, lint, post-change lint)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;code-contributor&lt;/code&gt; — writing and modifying code according to standards&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;code-architecture&lt;/code&gt; — architectural review (SOLID, DI, layered architecture)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;QWEN.md&lt;/code&gt; in the project root with a strict wiki-first rule and source hierarchy: &lt;code&gt;docs/wiki/ &amp;gt; Project code&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3. Initial Project Analysis (Initial Ingest)
&lt;/h3&gt;

&lt;p&gt;After creating the structure, the agent will execute &lt;strong&gt;Step 6&lt;/strong&gt; from the template:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scans the source code directories (&lt;code&gt;source_dirs&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Generates initial wiki pages: &lt;code&gt;Architecture.md&lt;/code&gt;, &lt;code&gt;Components.md&lt;/code&gt;, &lt;code&gt;Database.md&lt;/code&gt;, &lt;code&gt;Code Patterns.md&lt;/code&gt;, &lt;code&gt;Testing.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;index.md&lt;/code&gt;, adding new pages to the catalog.&lt;/li&gt;
&lt;li&gt;Adds an entry to &lt;code&gt;log.md&lt;/code&gt; about the initial knowledge load.&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Important:&lt;/strong&gt; The wiki contains only overviews and relative links to files. Copying code from &lt;code&gt;.cs&lt;/code&gt;, &lt;code&gt;.proto&lt;/code&gt;, or &lt;code&gt;.sql&lt;/code&gt; files is prohibited.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4. Verify Installation
&lt;/h3&gt;

&lt;p&gt;After the agent finishes, verify the result using the checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;QWEN.md&lt;/code&gt; contains the wiki-first rule and source priority&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;docs/wiki/&lt;/code&gt; contains &lt;code&gt;index.md&lt;/code&gt;, &lt;code&gt;log.md&lt;/code&gt;, &lt;code&gt;Wiki Format.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;.qwen/skills/&lt;/code&gt; contains 3 &lt;code&gt;SKILL.md&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;[ ] All skills explicitly state: &lt;code&gt;wiki &amp;gt; code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Base wiki pages for architecture, components, and patterns are generated&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;index.md&lt;/code&gt; contains links to all created pages&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;log.md&lt;/code&gt; contains an entry about the first ingest with date and description&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 How to Work with Wiki-First After Setup
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Developer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Before a task → open &lt;code&gt;docs/wiki/index.md&lt;/code&gt;. After committing → update wiki + add an entry to &lt;code&gt;log.md&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI Agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Any request → wiki first. After code changes → post-change lint. Missing details → read code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Code Review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If a PR didn't update the wiki → request changes. Wiki must not diverge from the code.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Typical AI agent workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Wiki-first&lt;/code&gt; → reads &lt;code&gt;index.md&lt;/code&gt; → finds relevant pages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Query/Ingest&lt;/code&gt; → clarifies details in code if needed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Code&lt;/code&gt; → writes feature/fix according to wiki patterns&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Post-Change Lint&lt;/code&gt; → updates wiki and &lt;code&gt;log.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Verify&lt;/code&gt; → checks links, frontmatter, code alignment&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🔄 Wiki Lifecycle: Maintenance, Consistency, and Skill Evolution
&lt;/h2&gt;

&lt;p&gt;Setting up wiki-first is not a one-time configuration, but the launch of a living process. Documentation left unattended quickly becomes a "knowledge graveyard." To ensure the approach delivers long-term value, the wiki must be continuously updated, and the AI agent must act as the primary guarantor of its consistency and completeness.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 Agent as the Guardian of Integrity
&lt;/h3&gt;

&lt;p&gt;After the initial setup, responsibility for wiki freshness falls on the AI agent. With every code change, the agent must automatically run a &lt;strong&gt;post-change lint&lt;/strong&gt;: check if existing pages contradict the new implementation, update descriptions of affected components, and log changes in &lt;code&gt;log.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Require explicit synchronization confirmation from the agent. If a commit affects architecture, configuration, or adds new modules, the agent must not only fix the code but also update the wiki. This turns documentation from an "extra burden" into an integral part of the development cycle. Without this step, the wiki will quickly drift from the code, and the &lt;code&gt;wiki &amp;gt; code&lt;/code&gt; rule will lose its meaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  📖 Documenting Complex Scenarios
&lt;/h3&gt;

&lt;p&gt;The true power of wiki-first emerges when not only static descriptions but also &lt;strong&gt;project-specific workflows&lt;/strong&gt; are added to the knowledge base.&lt;/p&gt;

&lt;p&gt;For example, adding a new configuration parameter in your project might require changes in five different places: from &lt;code&gt;appsettings.json&lt;/code&gt; to DI container objects, DTO models, and DB migrations. Instead of explaining this to the agent from scratch every time, document this scenario once as a separate wiki page or a section in &lt;code&gt;Code Patterns.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once a complex scenario is documented in the wiki, the agent begins executing it &lt;strong&gt;confidently, without hallucinations or missed steps&lt;/strong&gt;. When asked "Add parameter X", the agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finds the instruction in the wiki via &lt;code&gt;index.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Strictly follows the described sequence of changes.&lt;/li&gt;
&lt;li&gt;Automatically updates the wiki if the scenario needs adaptation to new realities.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The more "routine" and "easily forgotten" scenarios you formalize in the wiki, the less time is spent micromanaging the agent, and the higher the stability of the results.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Model-Specific Nature of Wiki
&lt;/h3&gt;

&lt;p&gt;Different LLM models structure thoughts, choose priorities, and formulate instructions differently. This is &lt;strong&gt;not a bug, but a feature&lt;/strong&gt;. When an agent itself generates the wiki during initial analysis (&lt;code&gt;Initial Ingest&lt;/code&gt;) or subsequent updates, the documentation naturally "adapts" to its internal logic, tokenization, and attention patterns.&lt;/p&gt;

&lt;p&gt;A model will follow a wiki it generated or curated itself much more accurately and confidently than documentation written by another model or a human "for themselves." Therefore, do not strive for perfect "human" stylistics in the early stages. The main thing is that the structure is unambiguous, links work, and the update logic is maintained. Over time, you will notice that the wiki becomes the "native language" of your agent: it finds context faster, hallucinates less, and executes complex tasks more precisely.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛠 Evolution of Skills and Rules
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;skills&lt;/code&gt; created during setup are not set in stone. As the project grows and experience with the agent accumulates, they must be adapted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the agent frequently misses a certain type of check, add the corresponding rule to &lt;code&gt;code-architecture&lt;/code&gt; or &lt;code&gt;wiki-workflow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If new tech stacks or patterns emerge, expand &lt;code&gt;code-contributor&lt;/code&gt; with examples and conventions.&lt;/li&gt;
&lt;li&gt;If the wiki structure becomes too bulky, refactor &lt;code&gt;index.md&lt;/code&gt;, introduce tags, or split monolithic pages into thematic blocks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;QWEN.md&lt;/code&gt; and &lt;code&gt;SKILL.md&lt;/code&gt; files themselves are part of the wiki ecosystem. They can and should be refactored to keep instructions precise, minimize context consumption, and match the current maturity stage of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Core Principle: Wiki is a Living Organism
&lt;/h3&gt;

&lt;p&gt;The key idea of the approach is this: &lt;strong&gt;you cannot just create a wiki, you must continuously develop it&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maintain freshness:&lt;/strong&gt; every code change → wiki change + entry in &lt;code&gt;log.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enrich context:&lt;/strong&gt; turn verbal agreements, bug trackers, and complex manual procedures into formalized wiki instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evolve the agent:&lt;/strong&gt; adapt skills, clarify priorities, and refine prompts for real project tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the wiki becomes the single source of truth, and the agent acts as its active curator, the team gets a scalable knowledge system. It doesn't "wither" over time but gains strength: the more you invest in it, the faster, more accurate, and more confident the AI works, reducing time spent on onboarding, code reviews, and bug fixing.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Conclusion
&lt;/h2&gt;

&lt;p&gt;The wiki-first approach transforms documentation from an "artifact that gets forgotten" into an &lt;strong&gt;active development tool&lt;/strong&gt;. AI agents operating under these rules save context, strictly adhere to architectural standards, and accelerate feature delivery.&lt;/p&gt;

&lt;p&gt;Set up wiki-first once using &lt;a href="https://github.com/abaula/DevInsights/blob/main/AIAssistantWikiFirst/WIKI_FIRST_TEMPLATE.en.md" rel="noopener noreferrer"&gt;WIKI_FIRST_TEMPLATE.en.md&lt;/a&gt;, integrate it into your CI/CD and onboarding processes, and you will get a scalable knowledge system that grows with your project rather than turning into legacy.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>assistant</category>
      <category>aiassistant</category>
    </item>
    <item>
      <title>Shadow Logging via Events - Complete Decoupling of Business Logic and Logging in DI Environment</title>
      <dc:creator>Anton B</dc:creator>
      <pubDate>Sun, 08 Feb 2026 09:51:27 +0000</pubDate>
      <link>https://dev.to/__3545da69e7b38a555/shadow-logging-via-events-complete-decoupling-of-business-logic-and-logging-in-di-environment-3kjf</link>
      <guid>https://dev.to/__3545da69e7b38a555/shadow-logging-via-events-complete-decoupling-of-business-logic-and-logging-in-di-environment-3kjf</guid>
      <description>&lt;p&gt;Hello, colleagues!&lt;/p&gt;

&lt;p&gt;I want to share an approach to logging that radically simplifies architecture and strengthens SOLID principles.&lt;/p&gt;

&lt;p&gt;I've created a code example on &lt;a href="https://github.com/abaula/MixedCode/blob/master/DecoupledLogging/src/Program.cs" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to demonstrate how shadow decoupled logging works through C# events.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Decoupled Logging?
&lt;/h1&gt;

&lt;p&gt;Decoupled logging is an architectural approach where business classes &lt;strong&gt;do not contain direct logger calls&lt;/strong&gt; (like &lt;code&gt;ILogger.LogInformation&lt;/code&gt;). Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business classes generate &lt;strong&gt;events&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specialized logger classes&lt;/strong&gt; handle the events.&lt;/li&gt;
&lt;li&gt;Business classes know nothing about loggers.&lt;/li&gt;
&lt;li&gt;Logging is configured &lt;strong&gt;centrally&lt;/strong&gt; at the application level, without polluting domain logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Pros and Cons of Decoupled Logging
&lt;/h1&gt;

&lt;p&gt;There are opinions both "for" and "against" decoupled logging.&lt;/p&gt;

&lt;p&gt;Typical arguments "for":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SRP purity&lt;/strong&gt;: The class focuses on business logic; logging is an external concern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability&lt;/strong&gt;: No &lt;code&gt;ILogger&lt;/code&gt; mocks needed; classes work standalone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility and scalability&lt;/strong&gt;: One event can be handled in all required ways: logs, metrics, audit. Easy to change event handling logic and logging libraries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decoupling for libraries&lt;/strong&gt;: Consumers of business logic classes decide themselves whether to log events and which ones. No hard dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Business class does not format data for logs. When implementing event handlers, use smart fast caching of incoming events with subsequent post-processing - it doesn't block business logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Critics' objections boil down to a simple thesis: don't complicate things if not really needed - "inject ILogger and don't worry." This opinion sounds reasonable; I agree with such criticism - if you have a simple application, don't complicate it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Extracting Logging from the Class
&lt;/h1&gt;

&lt;p&gt;The simplest way to separate business logic and logging is to write a Wrapper for the business class.&lt;/p&gt;

&lt;p&gt;Decorator/wrapper for logging is convenient but &lt;strong&gt;imposes usage rules&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client code must work through the wrapper.&lt;/li&gt;
&lt;li&gt;Refactoring becomes more complex.&lt;/li&gt;
&lt;li&gt;Duplication and inheritance issues arise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach makes logging &lt;strong&gt;not "shadow"&lt;/strong&gt; - consumers indirectly know about it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Complete Separation
&lt;/h1&gt;

&lt;p&gt;Complete separation of business logic and logging is possible only using two independent objects: business class and log handler.&lt;/p&gt;

&lt;p&gt;Simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderServiceLogger&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderServiceLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FileLogger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderService&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderCreated&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Order &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; created."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fileLogger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileLogger&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderServiceLogger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderServiceLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is straightforward, but if the application uses a DI container, it requires adaptation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Shadowing Logging
&lt;/h1&gt;

&lt;p&gt;DI containers perform 2 important tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Object factory&lt;/li&gt;
&lt;li&gt;Object lifetime management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Object creation is simple: the DI container will return 2 ready objects, with the log handler receiving the business class instance and logger instance upon creation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FileLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderServiceLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildServiceProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderServiceLogger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderServiceLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is managing the lifetime of the log handler &lt;code&gt;OrderServiceLogger&lt;/code&gt;, i.e., explicitly storing a reference to the created object and synchronizing its lifetime with the business class &lt;code&gt;OrderService&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;If we do nothing else, we'll have to explicitly create a new &lt;code&gt;OrderServiceLogger&lt;/code&gt; instance wherever we create an &lt;code&gt;OrderService&lt;/code&gt; instance and ensure their lifetimes match - that's not the behavior we want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use only business logic object instances in business logic, in our example &lt;code&gt;OrderService&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Business logic should know nothing about objects performing other tasks in the application, in our example logging via &lt;code&gt;OrderServiceLogger&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When creating a business logic object, the application must guarantee all implemented service functions for it - if &lt;code&gt;OrderServiceLogger&lt;/code&gt; is implemented for &lt;code&gt;OrderService&lt;/code&gt;, it must be created in time and handle events.&lt;/li&gt;
&lt;li&gt;Correct service function operation includes optimal application resource management - the &lt;code&gt;OrderServiceLogger&lt;/code&gt; instance must be removed from memory after the associated &lt;code&gt;OrderService&lt;/code&gt; object is destroyed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These requirements are easy to implement, even within a DI container. We've sorted object creation; now we need to implement lifetime synchronization using weak references.&lt;/p&gt;

&lt;p&gt;We need to ensure the created &lt;code&gt;OrderServiceLogger&lt;/code&gt; object lives no less than the &lt;code&gt;OrderService&lt;/code&gt; instance and is removed when no longer needed.&lt;/p&gt;

&lt;p&gt;For this, we need an application-level object that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores references to both dependent objects.&lt;/li&gt;
&lt;li&gt;Monitors their lifetimes.&lt;/li&gt;
&lt;li&gt;Removes &lt;code&gt;OrderServiceLogger&lt;/code&gt; as soon as &lt;code&gt;OrderService&lt;/code&gt; is removed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can implement such a class ourselves, where there is a &lt;strong&gt;key object&lt;/strong&gt; and &lt;strong&gt;dependent objects&lt;/strong&gt;. The architecture of such a class is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key object(s) are stored as weak references, which do not prevent the object from being garbage collected.&lt;/li&gt;
&lt;li&gt;Dependent objects are stored as strong references, which prevent the garbage collector from destroying them.&lt;/li&gt;
&lt;li&gt;The state of key objects is periodically checked - if they are removed, dependent objects are also removed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the simple case, we can use the &lt;a href="https://learn.microsoft.com/ru-ru/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2?view=net-8.0" rel="noopener noreferrer"&gt;ConditionalWeakTable&lt;/a&gt; class from the &lt;code&gt;System.Runtime.CompilerServices&lt;/code&gt; namespace, which already implements this logic.&lt;/p&gt;

&lt;h1&gt;
  
  
  Writing DI Logic
&lt;/h1&gt;

&lt;p&gt;Let's implement an extension method for &lt;code&gt;ServiceCollection&lt;/code&gt; and examine how it works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceCollectionExtensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ServiceCollection&lt;/span&gt; &lt;span class="n"&gt;AddScopedWithLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TServiceInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TServiceLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;ServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
        &lt;span class="nc"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TServiceInstance&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TService&lt;/span&gt;
        &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TServiceLogger&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Register TServiceInstance.&lt;/span&gt;
        &lt;span class="nc"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TServiceInstance&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Register TServiceLogger.&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TServiceLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Register TService.&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TServiceInstance&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TServiceLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conditionalWeakTable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="c1"&gt;// Put instance and logger into ConditionalWeakTable.&lt;/span&gt;
            &lt;span class="n"&gt;conditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;AddScopedWithLogger&lt;/code&gt; method does all the necessary work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Registers all types in DI.&lt;/li&gt;
&lt;li&gt;Implements the logic for creating and linking the business class and its event handler class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt; - in the DI container, it is necessary to separate the logic for creating the business class object itself from the logic for creating the instance with all its shadow objects. For this, it is best to use business class contracts (interfaces).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderEventArgs&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EventArgs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IOrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderEventArgs&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;OrderCreated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus, the DI container will pass the &lt;code&gt;OrderService&lt;/code&gt; instance to the &lt;code&gt;OrderServiceLogger&lt;/code&gt; constructor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In business logic, only contracts must be used&lt;/strong&gt;, which is a recommended approach.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderManager&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IOrderManager&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Correctly register all types in the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScopedWithLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderServiceLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, creating an object for its contract &lt;code&gt;IOrderService&lt;/code&gt; will trigger the following code from the extension method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Register TService.&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TServiceInstance&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TServiceLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conditionalWeakTable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Put instance and logger into ConditionalWeakTable.&lt;/span&gt;
    &lt;span class="n"&gt;conditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the breakdown for the parameter combination &lt;code&gt;IOrderService, OrderService, OrderServiceLogger&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderServiceLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conditionalWeakTable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Put instance and logger into ConditionalWeakTable.&lt;/span&gt;
    &lt;span class="n"&gt;conditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, it's simple. &lt;code&gt;OrderService&lt;/code&gt; and &lt;code&gt;OrderServiceLogger&lt;/code&gt; objects are created with all dependencies, then both objects are saved in the &lt;code&gt;ConditionalWeakTable&amp;lt;object, object&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conditionalWeakTable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Put instance and logger into ConditionalWeakTable.&lt;/span&gt;
&lt;span class="n"&gt;conditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ConditionalWeakTable&amp;lt;object, object&amp;gt;&lt;/code&gt; object itself must be registered in the DI container with a lifetime equal to or greater than &lt;code&gt;OrderService&lt;/code&gt; and &lt;code&gt;OrderServiceLogger&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I recommend using &lt;code&gt;Scoped&lt;/code&gt; if the registered objects live no longer. &lt;code&gt;Singleton&lt;/code&gt; is not necessary.&lt;/p&gt;

&lt;p&gt;And the last piece of the puzzle - at the application level, create a &lt;code&gt;ConditionalWeakTable&amp;lt;object, object&amp;gt;&lt;/code&gt; instance that lives no less than the objects stored in it.&lt;/p&gt;

&lt;p&gt;Simplest example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Registration of all types and other application service code&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;

        &lt;span class="c1"&gt;// Instance of ConditionalWeakTable that holds references to shadow objects.&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conditionalWeakTable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ConditionalWeakTable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Start application work.&lt;/span&gt;
        &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Advantages of the approach as I see them:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logger is automatically bound to a specific class instance.&lt;/li&gt;
&lt;li&gt;Weak references guarantee operation without memory leaks.&lt;/li&gt;
&lt;li&gt;Centralized subscription in the DI container.&lt;/li&gt;
&lt;li&gt;Ability to flexibly extend the number of shadow services and manage them.&lt;/li&gt;
&lt;li&gt;Strong SOLID with minimal compromises.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I recommend using it for serious projects where quality architecture provides a tangible advantage.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>dotnet</category>
      <category>backend</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
