<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[StepOne]]></title><description><![CDATA[Deep C# and .NET internals, compiler and type-system ideas, testing without ceremony, and whether popular abstractions earn their cost in production code.]]></description><link>https://steponeit.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a8a0b6e88b6e2124307755b/7d403018-39e3-4874-a583-30ad41f2ec01.jpg</url><title>StepOne</title><link>https://steponeit.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 31 Aug 2026 11:09:48 GMT</lastBuildDate><atom:link href="https://steponeit.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Two Biggest MediatR Mistakes
]]></title><description><![CDATA[MediatR is useful at an application boundary: a caller sends one request, and one handler owns that use case. Two common shortcuts weaken that model—sending more requests from inside a handler and com]]></description><link>https://steponeit.hashnode.dev/two-biggest-mediatr-mistakes</link><guid isPermaLink="true">https://steponeit.hashnode.dev/two-biggest-mediatr-mistakes</guid><category><![CDATA[dotnet]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[backend]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[StepOne]]></dc:creator><pubDate>Sun, 30 Aug 2026 18:20:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a8a0b6e88b6e2124307755b/db23472c-09af-4de2-afca-63470616c99d.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>MediatR is useful at an application boundary: a caller sends one request, and one handler owns that use case. Two common shortcuts weaken that model—sending more requests from inside a handler and combining several request contracts in one handler class.</p>
<p>Both shortcuts look like ways to reuse dependencies or avoid small classes. In practice, they hide orchestration, make navigation harder, and couple use cases that should be independently testable and replaceable. This article explains the failure modes and the simpler boundaries that avoid them.</p>
<h2>Mistake 1: Calling One MediatR Handler from Another</h2>
<p>Consider the following situation. Suppose you need to handle a message:</p>
<pre><code class="language-csharp">public class PingHandler : IRequestHandler&lt;Ping, string&gt;
{
    public Task&lt;string&gt; Handle(Ping request, CancellationToken token = default) =&gt;
        Task.FromResult("Pong");
}
</code></pre>
<p>Then a new requirement arrives: after handling this message, you need to process other messages based on either the original handler's result or the incoming message. One tempting implementation looks like this:</p>
<pre><code class="language-csharp">public class PingHandler : IRequestHandler&lt;Ping, string&gt;
{
    private readonly IMediator _mediator;

    public PingHandler(IMediator mediator) =&gt; _mediator = mediator;

    public async Task&lt;string&gt; Handle(Ping request, CancellationToken token = default)
    {
        await _mediator.Send(new AfterPing(request), token);
        return "Pong";
    }
}
</code></pre>
<p>Keep this orchestration out of the handler.</p>
<p>First, it is confusing. MediatR already makes IntelliSense navigation harder; there is no need to make it worse.</p>
<p>It is also worth remembering that, by design, MediatR serves as an <strong>external</strong> bridge to your application's actual behavior—behavior that is specific to the domain you have chosen. That is precisely what the cover image illustrates.</p>
<p>Calling other messages from inside a handler is therefore an anti-pattern in the context of the library itself. In the words of MediatR's author:</p>
<blockquote>
<p>The indirection of a handler is good at the application level, but just got confusing once we got inside a handler (and it introduced coupling).</p>
</blockquote>
<p>The tool exists to provide request-response entry points at the top level of the application while minimizing coupling. The code above does exactly the opposite.</p>
<p>There are several ways to keep the orchestration visible:</p>
<ul>
<li><p>Return the data needed to construct the next message, then send it at the higher level where the original handler was called—the simplest option.</p>
</li>
<li><p>Move the shared logic into an application or domain service.</p>
</li>
<li><p>Use an extension method for genuinely local, stateless reuse.</p>
</li>
<li><p>Use a MediatR pipeline behavior for cross-cutting concerns.</p>
</li>
</ul>
<h2>Mistake 2: Handling Multiple MediatR Requests in One Handler</h2>
<p>Suppose you have a group of messages that work with the same entity. They have roughly the same dependencies, so instead of writing several handlers, you combine them into one and end up with something like this:</p>
<pre><code class="language-csharp">public class MyEntityRequestHandler :
    IRequestHandler&lt;CreateMyEntityRequest, MyEntity&gt;,
    IRequestHandler&lt;GetMyEntityRequest, MyEntity&gt;,
    IRequestHandler&lt;GetAllMyEntitiesRequest, List&lt;MyEntity&gt;&gt;;
</code></pre>
<p>Once again, this goes against the library's core idea: splitting an application into a set of distinct requests to improve flexibility and maintainability. This approach takes us back to bloated interfaces and violates the Interface Segregation Principle (ISP).</p>
<p>Again, in the library author's words:</p>
<blockquote>
<p>Don't combine your handlers, keep them separate, reduce coupling across handlers.</p>
</blockquote>
<h2>Better MediatR Handler Boundaries</h2>
<p>Keep orchestration visible at the application boundary, and keep one handler focused on one request contract. Shared behavior can still live in domain services, application services, decorators, or pipeline behaviors; it does not need to be hidden behind another mediator call.</p>
<p>These are design rules, not syntax rules. If a workflow genuinely coordinates several use cases, model that workflow explicitly and test it as such. The warning is against accidental handler graphs whose control flow can be discovered only by tracing <code>Send</code> calls.</p>
<h2>MediatR Handler FAQ</h2>
<h3>Should a MediatR handler call another handler?</h3>
<p>Usually, no. Put workflow orchestration at the application boundary and move reusable behavior into an application service, domain service, decorator, or pipeline behavior. That keeps the sequence visible and prevents handlers from becoming an implicit call graph.</p>
<h3>Can one class implement several <code>IRequestHandler</code> interfaces?</h3>
<p>The type system allows it, but sharing a class couples request contracts that should be independently navigable, testable, and replaceable. Separate handler classes can still depend on the same lower-level service when they genuinely share behavior.</p>
<h3>When should I use a MediatR pipeline behavior?</h3>
<p>Use a pipeline behavior for concerns that consistently wrap many requests, such as validation, logging, or transaction handling. Do not use it to hide a business workflow whose sequence is important to the use case.</p>
<p>You can read more about the subject in these sources:</p>
<ul>
<li><p><a href="https://lostechies.com/jimmybogard/2016/12/12/dealing-with-duplication-in-mediatr-handlers/">Dealing with Duplication in MediatR Handlers</a></p>
</li>
<li><p><a href="https://github.com/jbogard/MediatR/issues/400">MediatR issue #400</a></p>
</li>
<li><p><a href="https://github.com/jbogard/MediatR/issues/434">MediatR issue #434</a></p>
</li>
<li><p><a href="https://github.com/jbogard/MediatR/issues/281">MediatR issue #281</a></p>
</li>
</ul>
<hr />
<p>Follow <a href="https://github.com/Stepami">Stepami on GitHub</a> for open-source C#/.NET projects, compiler experiments, production-focused examples, and new releases.</p>
]]></content:encoded></item><item><title><![CDATA[Neural Network from Scratch in C#]]></title><description><![CDATA[Building a neural network without a framework is a useful way to understand forward propagation, backpropagation, activation functions, and weight updates as one working system. This article implement]]></description><link>https://steponeit.hashnode.dev/neural-network-from-scratch-in-csharp</link><guid isPermaLink="true">https://steponeit.hashnode.dev/neural-network-from-scratch-in-csharp</guid><category><![CDATA[C#]]></category><category><![CDATA[AI]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[StepOne]]></dc:creator><pubDate>Sat, 29 Aug 2026 15:40:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a8a0b6e88b6e2124307755b/449b15b3-dbc7-4660-9cf2-7c1eb48a0bcb.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Building a neural network without a framework is a useful way to understand forward propagation, backpropagation, activation functions, and weight updates as one working system. This article implements a small fully connected network in C# and trains it to solve XOR and XNOR.</p>
<h2>Prerequisites and Scope</h2>
<p>This is a historical article from 2017, not production ML guidance. It targets C# 7 and .NET Framework 4.7, deliberately omits bias terms, and preserves the original code—including its comments and formatting. Treat it as an under-the-hood learning exercise; for a production system, use a maintained ML framework and current hardware support.</p>
<p>The walkthrough assumes that you already understand the basic mathematics of neural networks. If you need a deeper foundation, Simon Haykin's <em>Neural Networks: A Comprehensive Foundation</em> explains the mechanics in detail.</p>
<p>I originally built a handwritten-digit recognizer as a school project, then reduced the idea to a network small enough to inspect piece by piece. The smaller problem makes every part of the training loop visible instead of hiding it behind a framework API.</p>
<h2>Neural Network Architecture</h2>
<p>Before writing any code, you should draw the network on paper. That makes its structure and behavior easier to visualize. My sketch became the diagram below. And yes, this is a console application in Visual Studio 2017 targeting .NET Framework 4.7.</p>
<p><strong>Network at a glance</strong></p>
<ul>
<li><p>Multilayer fully connected perceptron.</p>
</li>
<li><p>One hidden layer.</p>
</li>
<li><p>Four neurons in the hidden layer—the perceptron converged with this number.</p>
</li>
<li><p>Training algorithm: backpropagation.</p>
</li>
<li><p>Stopping criterion: the mean squared error for an epoch falls below a threshold of 0.001.</p>
</li>
<li><p>Learning rate: 0.1.</p>
</li>
<li><p>Activation function: logistic sigmoid.</p>
</li>
</ul>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qqoupwixpadqxy6xkdg6.png" alt="Neural network with input, hidden, and output layers" style="display:block;margin:0 auto" />

<p>Next, we need somewhere to store the weights, perform calculations, do a little debugging, and make some use of tuples. These are our <code>using</code> directives.</p>
<h3>Weight Storage Files</h3>
<p>The project's <code>release</code> or <code>debug</code> directory contains one file per layer, named something like <em>(fieldname)_memory.xml</em>. You can probably guess what they are for. The files are created in advance with the total number of weights in each layer. I know XML is not the best parsing format; I simply did not have much time.</p>
<pre><code class="language-csharp">using System.Xml;
using static System.Math;
using static System.Console;
</code></pre>
<h2>Implementing the Neural Network in C#</h2>
<p>We have two kinds of computational neurons: hidden and output. Weights can be read from or written to storage. We represent those concepts with two enums.</p>
<pre><code class="language-csharp">enum MemoryMode
{ 
    GET,
    SET
}

enum NeuronType
{
    Hidden,
    Output
}
</code></pre>
<p>Everything else lives in a namespace I will simply call <code>NeuralNetwork</code>.</p>
<h3>Input Layer and XOR/XNOR Training Data</h3>
<p>First, why did I draw the input-layer neurons as squares? They calculate nothing. They capture information from the outside world—the signal that will pass through the network—so the input layer has little in common with the others.</p>
<p>Should it get a separate class? For image, video, or audio processing, it should: the class gives you somewhere to transform and normalize data into the form expected by the network. That is why I will write an <code>InputLayer</code> class after all. It contains a training set organized in an unusual structure. The first array in each tuple contains combinations of 1 and 0. The second contains the corresponding XOR and XNOR results, in that order.</p>
<pre><code class="language-csharp">class InputLayer
{
    private (double[], double[])[] _trainset = new (double[], double[])[]
    {
        (new double[] { 0, 0 }, new double[] { 0, 1 }),
        (new double[] { 0, 1 }, new double[] { 1, 0 }),
        (new double[] { 1, 0 }, new double[] { 1, 0 }),
        (new double[] { 1, 1 }, new double[] { 0, 1 }),
    };
    public (double[], double[])[] Trainset
    {
        get =&gt; _trainset;
    }
}
</code></pre>
<h3>Neurons, Activation, and Gradients</h3>
<p>Now for the most important part, without which no neural network can become a Terminator: the neuron. I will omit bias terms here. The neuron resembles the McCulloch-Pitts model, but replaces the threshold function with another activation function. It also has methods for calculating gradients and derivatives, its own type, and combined linear and nonlinear transforms. Naturally, we also need a constructor.</p>
<pre><code class="language-csharp">class Neuron
{
    public Neuron(double[] inputs, double[] weights, NeuronType type)
    {
        _type = type;
        _weights = weights;
        _inputs = inputs;
    }

    private NeuronType _type;
    private double[] _weights;
    private double[] _inputs;
    public double[] Weights
    {
        get =&gt; _weights;
        set =&gt; _weights = value;
    }
    public double[] Inputs
    {
        get =&gt; _inputs;
        set =&gt; _inputs = value;
    }
    public double Output
    {
        get =&gt; Activator(_inputs, _weights);
    }

    private double Activator(double[] i, double[] w)
    {
        double sum = 0;
        for (int l = 0; l &lt; i.Length; ++l)
            sum += i[l] * w[l];
        return Pow(1 + Exp(-sum), -1);
    }

    public double Derivativator(double outsignal) =&gt; outsignal * (1 - outsignal);

    public double Gradientor(double error, double dif, double g_sum) =&gt;
        (_type == NeuronType.Output) ? error * dif : g_sum * dif;
}
</code></pre>
<h3>Hidden and Output Layers</h3>
<p>We have neurons, but they need to be grouped into layers for computation. Look back at the diagram and note the black dashed line. It separates the layers to show what each contains. A computational layer contains neurons and the weights connecting them to the <strong>previous</strong> layer's neurons.</p>
<p>Neurons are stored in an array rather than a list to reduce overhead. The weights form a matrix—a two-dimensional array—with dimensions <code>[number of neurons in the current layer **x** number of neurons in the previous layer]</code>. The layer must initialize its neurons or we will get a null reference. The layers are structurally similar but differ in their logic, so the hidden and output layers should inherit from one abstract base class.</p>
<pre><code class="language-csharp">abstract class Layer
{
    protected Layer(int non, int nopn, NeuronType nt, string type)
    {
        numofneurons = non;
        numofprevneurons = nopn;
        Neurons = new Neuron[non];
        double[,] Weights = WeightInitialize(MemoryMode.GET, type);
        for (int i = 0; i &lt; non; ++i)
        {
            double[] temp_weights = new double[nopn];
            for (int j = 0; j &lt; nopn; ++j)
                temp_weights[j] = Weights[i, j];
            Neurons[i] = new Neuron(null, temp_weights, nt);
        }
    }

    protected int numofneurons;
    protected int numofprevneurons;
    protected const double learningrate = 0.1d;
    Neuron[] _neurons;
    public Neuron[] Neurons
    {
        get =&gt; _neurons;
        set =&gt; _neurons = value;
    }
    public double[] Data
    {
        set
        {
            for (int i = 0; i &lt; Neurons.Length; ++i)
                Neurons[i].Inputs = value;
        }
    }

    public double[,] WeightInitialize(MemoryMode mm, string type)
    {
        double[,] _weights = new double[numofneurons, numofprevneurons];
        WriteLine($"{type} weights are being initialized...");
        XmlDocument memory_doc = new XmlDocument();
        memory_doc.Load($"{type}_memory.xml");
        XmlElement memory_el = memory_doc.DocumentElement;
        switch (mm)
        {
            case MemoryMode.GET:
                for (int l = 0; l &lt; _weights.GetLength(0); ++l)
                for (int k = 0; k &lt; _weights.GetLength(1); ++k)
                    _weights[l, k] = double.Parse(
                        memory_el
                            .ChildNodes.Item(k + _weights.GetLength(1) * l)
                            .InnerText.Replace(',', '.'),
                        System.Globalization.CultureInfo.InvariantCulture
                    );
                break;
            case MemoryMode.SET:
                for (int l = 0; l &lt; Neurons.Length; ++l)
                for (int k = 0; k &lt; numofprevneurons; ++k)
                    memory_el.ChildNodes.Item(k + numofprevneurons * l).InnerText = Neurons[l]
                        .Weights[k]
                        .ToString();
                break;
        }
        memory_doc.Save($"{type}_memory.xml");
        WriteLine($"{type} weights have been initialized...");
        return _weights;
    }

    public abstract void Recognize(Network net, Layer nextLayer);
    public abstract double[] BackwardPass(double[] stuff);
}
</code></pre>
<p><strong>Why abstract classes matter</strong></p>
<p><code>Layer</code> is abstract, so it cannot be instantiated. We preserve the properties of a layer through inheritance: the derived constructor calls the parent constructor with <code>base</code> and otherwise fits on one line, because all constructor logic is already defined in the base class and need not be repeated.</p>
<p>Now for the derived classes themselves, <code>HiddenLayer</code> and <code>OutputLayer</code>, presented together in one block.</p>
<pre><code class="language-csharp">class HiddenLayer : Layer
{
    public HiddenLayer(int non, int nopn, NeuronType nt, string type)
        : base(non, nopn, nt, type) { }

    public override void Recognize(Network net, Layer nextLayer)
    {
        double[] hidden_out = new double[Neurons.Length];
        for (int i = 0; i &lt; Neurons.Length; ++i)
            hidden_out[i] = Neurons[i].Output;
        nextLayer.Data = hidden_out;
    }

    public override double[] BackwardPass(double[] gr_sums)
    {
        double[] gr_sum = null;
        for (int i = 0; i &lt; numofneurons; ++i)
        for (int n = 0; n &lt; numofprevneurons; ++n)
            Neurons[i].Weights[n] +=
                learningrate
                * Neurons[i].Inputs[n]
                * Neurons[i].Gradientor(0, Neurons[i].Derivativator(Neurons[i].Output), gr_sums[i]);
        return gr_sum;
    }
}

class OutputLayer : Layer
{
    public OutputLayer(int non, int nopn, NeuronType nt, string type)
        : base(non, nopn, nt, type) { }

    public override void Recognize(Network net, Layer nextLayer)
    {
        for (int i = 0; i &lt; Neurons.Length; ++i)
            net.fact[i] = Neurons[i].Output;
    }

    public override double[] BackwardPass(double[] errors)
    {
        double[] gr_sum = new double[numofprevneurons];
        for (int j = 0; j &lt; gr_sum.Length; ++j)
        {
            double sum = 0;
            for (int k = 0; k &lt; Neurons.Length; ++k)
                sum +=
                    Neurons[k].Weights[j]
                    * Neurons[k]
                        .Gradientor(errors[k], Neurons[k].Derivativator(Neurons[k].Output), 0);
            gr_sum[j] = sum;
        }
        for (int i = 0; i &lt; numofneurons; ++i)
        for (int n = 0; n &lt; numofprevneurons; ++n)
            Neurons[i].Weights[n] +=
                learningrate
                * Neurons[i].Inputs[n]
                * Neurons[i].Gradientor(errors[i], Neurons[i].Derivativator(Neurons[i].Output), 0);
        return gr_sum;
    }
}
</code></pre>
<h3>Training with Backpropagation</h3>
<p>The comments describe the important details. We now have every component: training and test data, computational elements, and layers. It is time to connect them through training. The algorithm is backpropagation, and the stopping criterion is a mean squared error below 0.001 for an epoch. The <code>Network</code> class holds the network state passed among the methods.</p>
<pre><code class="language-csharp">class Network
{
    InputLayer input_layer = new InputLayer();
    public HiddenLayer hidden_layer = new HiddenLayer(
        4,
        2,
        NeuronType.Hidden,
        nameof(hidden_layer)
    );
    public OutputLayer output_layer = new OutputLayer(
        2,
        4,
        NeuronType.Output,
        nameof(output_layer)
    );
    public double[] fact = new double[2];

    double GetMSE(double[] errors)
    {
        double sum = 0;
        for (int i = 0; i &lt; errors.Length; ++i)
            sum += Pow(errors[i], 2);
        return 0.5d * sum;
    }

    double GetCost(double[] mses)
    {
        double sum = 0;
        for (int i = 0; i &lt; mses.Length; ++i)
            sum += mses[i];
        return (sum / mses.Length);
    }

    static void Train(Network net)
    {
        const double threshold = 0.001d;
        double[] temp_mses = new double[4];
        double temp_cost = 0;
        do
        {
            for (int i = 0; i &lt; net.input_layer.Trainset.Length; ++i)
            {
                net.hidden_layer.Data = net.input_layer.Trainset[i].Item1;
                net.hidden_layer.Recognize(null, net.output_layer);
                net.output_layer.Recognize(net, null);
                double[] errors = new double[net.input_layer.Trainset[i].Item2.Length];
                for (int x = 0; x &lt; errors.Length; ++x)
                    errors[x] = net.input_layer.Trainset[i].Item2[x] - net.fact[x];
                temp_mses[i] = net.GetMSE(errors);
                double[] temp_gsums = net.output_layer.BackwardPass(errors);
                net.hidden_layer.BackwardPass(temp_gsums);
            }
            temp_cost = net.GetCost(temp_mses);
            WriteLine($"{temp_cost}");
        } while (temp_cost &gt; threshold);
        net.hidden_layer.WeightInitialize(MemoryMode.SET, nameof(hidden_layer));
        net.output_layer.WeightInitialize(MemoryMode.SET, nameof(output_layer));
    }

    static void Test(Network net)
    {
        for (int i = 0; i &lt; net.input_layer.Trainset.Length; ++i)
        {
            net.hidden_layer.Data = net.input_layer.Trainset[i].Item1;
            net.hidden_layer.Recognize(null, net.output_layer);
            net.output_layer.Recognize(net, null);
            for (int j = 0; j &lt; net.fact.Length; ++j)
                WriteLine($"{net.fact[j]}");
            WriteLine();
        }
    }

    static void Main(string[] args)
    {
        Network net = new Network();
        Train(net);
        Test(net);
        ReadKey();
    }
}
</code></pre>
<h2>Training Result and Limitations</h2>
<p>The training result:</p>
<img src="https://habrastorage.org/r/w1560/web/dc6/bfc/208/dc6bfc208a8c47d3b270aa5f71728aad.png" alt="image" style="display:block;margin:0 auto" />

<p>After these <del>brain-breaking</del> straightforward manipulations, we have the foundation of a working neural network. To make it do something else, change the <code>InputLayer</code> class and choose suitable network parameters for the new task.</p>
<p>That is all. I will be happy to answer questions in the comments, but for now I have other things to do.<br />P.S. If you want to try the code, <a href="https://yadi.sk/d/7lTaGYyR3Lk7cX">click here</a>.</p>
<p>UPDATE 1 (October 22, 2020): Good grief, that was a long time ago. I hope I never write articles like this again. At the time, I probably wanted to share <a href="https://github.com/StefanioHabrArticles/oomlcs">code like this</a> with the community, but nobody writes ML this way.</p>
<p>UPDATE 2 (December 17, 2022): <a href="https://github.com/StefanioHabrArticles/recognitor">Recognizing 3 x 5 pixel images</a></p>
<hr />
<p>Follow <a href="https://github.com/Stepami">Stepami on GitHub</a> for open-source C#/.NET projects, compiler experiments, production-focused examples, and new releases.</p>
]]></content:encoded></item></channel></rss>