<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://yohdeadfall.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://yohdeadfall.github.io/" rel="alternate" type="text/html" /><updated>2025-07-11T22:07:20+00:00</updated><id>https://yohdeadfall.github.io/feed.xml</id><title type="html">One dev’s thoughts</title><subtitle>Varous thoughts on programming and tech</subtitle><entry><title type="html">Lightweight observer chains</title><link href="https://yohdeadfall.github.io/lightweight-observer-chains" rel="alternate" type="text/html" title="Lightweight observer chains" /><published>2025-06-13T00:00:00+00:00</published><updated>2025-06-13T00:00:00+00:00</updated><id>https://yohdeadfall.github.io/lightweight-observer-chains</id><content type="html" xml:base="https://yohdeadfall.github.io/lightweight-observer-chains"><![CDATA[<p>Previously I wrote about another approach making properties observable for reactive programming in .NET using the Kinetic project. This time I would like to talk about a non-less interesting feature there, boxing an observer chain into a single-state machine.</p>

<p>The idea behind that isn’t new at all. You might hear about it from projects trying to make LINQ for <code class="language-plaintext highlighter-rouge">IEnumerable&lt;T&gt;</code> to be zero-allocating. That’s not possible for the observable pattern as it involves subscriptions, but what is achievable here is memory pressure reduction.</p>

<h1 id="take-one-struct-it">Take one: struct it</h1>

<p>As I mentioned earlier, the main difference between the interactive and reactive patterns lies in the direction of the control flow. In the first case a program requests data from an iterator that can be sourced by another one, and so on making a chain that executes backward. That’s a request-response interaction, and there’s just one party asking for data. In reactive programming, data is published into topics that anyone can subscribe to and react to when a notification comes. There a chain of operators is executed exactly in the same order as it was written, and each operator can have multiple subscribers, but in most cases, there’s just one listener. That’s a real prodigal.</p>

<p>So, how would one fix that? Surely not without structs as we can pack everything into a single state machine and allow the JIT compiler to inline every call between the operators that can be inlined.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">interface</span> <span class="nc">IOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IObserver</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;,</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
<span class="p">{</span> <span class="p">}</span>

<span class="k">struct</span> <span class="nc">SelectOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">,</span> <span class="n">TContinuation</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span>
    <span class="k">where</span> <span class="n">TContinuation</span> <span class="p">:</span> <span class="n">IObserver</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
<span class="p">{</span> <span class="p">}</span>

<span class="k">struct</span> <span class="nc">WhereOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TContinuation</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TSource</span><span class="p">&gt;</span>
    <span class="k">where</span> <span class="n">TContinuation</span> <span class="p">:</span> <span class="n">IObserver</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span>
<span class="p">{</span> <span class="p">}</span>

<span class="k">struct</span> <span class="nc">PublishOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TSource</span><span class="p">&gt;</span>
<span class="p">{</span> <span class="p">}</span>
</code></pre></div></div>

<p>And then a simple <code class="language-plaintext highlighter-rouge">x.Where(...).Select(...)</code> expression can be written as:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="n">SelectOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">,</span> <span class="n">WhereOperator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">,</span> <span class="n">PublishOperator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;&gt;&gt;(</span>
    <span class="k">new</span> <span class="n">WhereOperator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">,</span> <span class="n">PublishOperator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;&gt;(</span>
        <span class="k">new</span> <span class="n">PublishOperator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;())):</span>
</code></pre></div></div>

<p>Definitely, it’s not how anyone would like to do it. Hopefully, we have the builder pattern.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IOperatorFactory</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="n">IDisposable</span> <span class="n">ApplyOperator</span><span class="p">&lt;</span><span class="n">TContinuation</span><span class="p">&gt;(</span><span class="n">TContinuation</span> <span class="n">continuation</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TContinuation</span> <span class="p">:</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;;</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">struct</span> <span class="nc">SelectOperatorFactory</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">,</span> <span class="n">TPrevious</span><span class="p">&gt;</span>
    <span class="k">where</span> <span class="n">TPrevious</span> <span class="p">:</span> <span class="n">IOperatorFactory</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">Select</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="n">TPrevious</span> <span class="n">Previous</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span>

    <span class="k">public</span> <span class="n">IDisposable</span> <span class="n">ApplyOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TContinuation</span><span class="p">&gt;(</span><span class="n">TContinuation</span> <span class="n">continuation</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TContinuation</span> <span class="p">:</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">Previous</span><span class="p">.</span><span class="n">ApplyOperator</span><span class="p">&lt;</span><span class="n">SelectOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">,</span> <span class="n">TContinuation</span><span class="p">&gt;&gt;(</span>
            <span class="k">new</span><span class="p">(</span><span class="n">continuation</span><span class="p">,</span> <span class="n">Select</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">ObserverExtensions</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">SelectOperatorFactory</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">,</span> <span class="n">TPrevious</span><span class="p">&gt;</span> <span class="nf">Select</span><span class="p">(</span><span class="k">this</span> <span class="n">TPrevious</span> <span class="n">source</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="k">select</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TPrevious</span> <span class="p">:</span> <span class="n">IOperatorFactory</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="n">Previous</span> <span class="p">=</span> <span class="n">previous</span><span class="p">,</span>
            <span class="n">Select</span> <span class="p">=</span> <span class="k">select</span><span class="p">,</span>
        <span class="p">};</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It looks good so far, but it won’t work due to a Roslyn’s limitation. It isn’t good at generic argument deduction and that’s by design.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;().</span><span class="nf">M</span><span class="p">();</span> <span class="c1">// error CS0411: The type arguments for method 'C.M&lt;X, Y&gt;(X)' cannot be inferred from the usage. Try specifying the type arguments explicitly.</span>

<span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">C</span> <span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="n">M</span><span class="p">&lt;</span><span class="n">X</span><span class="p">,</span> <span class="n">Y</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">X</span> <span class="n">x</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">X</span> <span class="p">:</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Y</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>One potential workaround is adding operator creation methods to each factory. That’s too much even with help from source generators, plus it won’t be extensible outside of that library. That really sucks, but there are two more options at least not counting that we can use another language as it’s a non-goal.</p>

<h1 id="take-two-virtual-generic-dispatch">Take two: virtual generic dispatch</h1>

<p>Let’s reimagine what was previously done by dropping complexity that Roslyn cannot handle. First, we need to reduce our operator type to something simple enough, a type with a minimal number of generic arguments.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Operator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
<span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Yeah, that’s almost <code class="language-plaintext highlighter-rouge">IObservable&lt;T&gt;</code> and it has no problems with extension method calls. Now let’s add a derived type, an operator too, but one that has an idea what its ancestor is.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Operator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">protected</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="n">Source</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>

    <span class="k">protected</span> <span class="nf">Operator</span><span class="p">(</span><span class="n">Operator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Source</span> <span class="p">=</span> <span class="n">source</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Last, there should be a method that allows building a single stack machine based on structs. For that we can utilize virtual generic calls, a quite unique dispatch available for us in .NET.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IStateMachine</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IObserver</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;,</span> <span class="n">IDisposable</span>
<span class="p">{</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">interface</span> <span class="nc">IStateMachineBoxFactory</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="n">TBox</span> <span class="n">Create</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TStateMachine</span><span class="p">&gt;(</span><span class="k">in</span> <span class="n">TStateMachine</span> <span class="n">stateMachine</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TStateMachine</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachine</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;;</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Operator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">abstract</span> <span class="n">TBox</span> <span class="n">Build</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">,</span> <span class="n">TBoxFactory</span><span class="p">,</span> <span class="n">TContinuation</span><span class="p">&gt;(</span><span class="k">in</span> <span class="n">TBoxFactory</span> <span class="n">boxFactory</span><span class="p">,</span> <span class="k">in</span> <span class="n">TContinuation</span> <span class="n">continuation</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TBoxFactory</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachineBoxFactory</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">&gt;</span>
        <span class="k">where</span> <span class="n">TContinuation</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachine</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A simple <code class="language-plaintext highlighter-rouge">Select</code> operator can be expressed like another class derived from <code class="language-plaintext highlighter-rouge">Operator&lt;TSource, TResult&gt;</code> with the corresponding state machine:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">SelectOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">_selector</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">SelectOperator</span><span class="p">(</span><span class="n">Operator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">)</span> <span class="p">:</span>
        <span class="k">base</span><span class="p">(</span><span class="n">source</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_selector</span> <span class="p">=</span> <span class="n">selector</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">override</span> <span class="n">TBox</span> <span class="n">Build</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">,</span> <span class="n">TBoxFactory</span><span class="p">,</span> <span class="n">TContinuation</span><span class="p">&gt;(</span><span class="k">in</span> <span class="n">TBoxFactory</span> <span class="n">boxFactory</span><span class="p">,</span> <span class="k">in</span> <span class="n">TContinuation</span> <span class="n">continuation</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="n">Source</span><span class="p">.</span><span class="n">Build</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">,</span> <span class="n">TBoxFactory</span><span class="p">,</span> <span class="n">StateMachine</span><span class="p">&lt;</span><span class="n">TContinuation</span><span class="p">&gt;&gt;(</span><span class="n">boxFactory</span><span class="p">,</span> <span class="k">new</span><span class="p">(</span><span class="n">continuation</span><span class="p">,</span> <span class="n">_selector</span><span class="p">));</span>

    <span class="k">private</span> <span class="k">struct</span> <span class="nc">StateMachine</span><span class="p">&lt;</span><span class="n">TContinuation</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IStateMachine</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span>
        <span class="k">where</span> <span class="n">TContinuation</span><span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachine</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
    <span class="p">{</span>
        <span class="k">private</span> <span class="n">TContinuation</span> <span class="n">_continuation</span><span class="p">;</span>
        <span class="k">private</span> <span class="k">readonly</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">_selector</span><span class="p">;</span>

        <span class="k">public</span> <span class="nf">StateMachine</span><span class="p">(</span><span class="k">in</span> <span class="n">TContinuation</span> <span class="n">continuation</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">selector</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">_continuation</span> <span class="p">=</span> <span class="n">continuation</span><span class="p">;</span>
            <span class="n">_selector</span> <span class="p">=</span> <span class="n">selector</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">public</span> <span class="k">void</span> <span class="nf">OnNext</span><span class="p">(</span><span class="n">TSource</span> <span class="k">value</span><span class="p">)</span> <span class="p">=&gt;</span>
            <span class="n">_continuation</span><span class="p">.</span><span class="nf">OnNext</span><span class="p">(</span><span class="nf">_selector</span><span class="p">(</span><span class="k">value</span><span class="p">));</span>

        <span class="k">public</span> <span class="k">void</span> <span class="nf">OnError</span><span class="p">(</span><span class="n">Exception</span> <span class="n">error</span><span class="p">)</span> <span class="p">=&gt;</span>
            <span class="n">_continuation</span><span class="p">.</span><span class="nf">OnError</span><span class="p">(</span><span class="n">error</span><span class="p">);</span>

        <span class="k">public</span> <span class="k">void</span> <span class="nf">OnCompleted</span><span class="p">(</span><span class="n">TSource</span> <span class="k">value</span><span class="p">)</span> <span class="p">=&gt;</span>
            <span class="n">_continuation</span><span class="p">.</span><span class="nf">OnCompleted</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Other operators aren’t different, but all of them end up in calling the <code class="language-plaintext highlighter-rouge">Build</code> method on the <code class="language-plaintext highlighter-rouge">Source</code> though, and there should be one operator that doesn’t do that.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">BoxOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="n">_source</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">BoxOperator</span><span class="p">(</span><span class="n">IObservable</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="n">_source</span> <span class="p">=</span> <span class="n">source</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">override</span> <span class="n">TBox</span> <span class="n">Build</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">,</span> <span class="n">TBoxFactory</span><span class="p">,</span> <span class="n">TContinuation</span><span class="p">&gt;(</span><span class="k">in</span> <span class="n">TBoxFactory</span> <span class="n">boxFactory</span><span class="p">,</span> <span class="k">in</span> <span class="n">TContinuation</span> <span class="n">continuation</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">box</span> <span class="p">=</span> <span class="n">boxFactory</span><span class="p">.</span><span class="n">Create</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">Continuation</span><span class="p">&gt;(</span><span class="n">boxFactory</span><span class="p">,</span> <span class="k">new</span><span class="p">(</span><span class="n">continuation</span><span class="p">,</span> <span class="n">_selector</span><span class="p">));</span>

        <span class="c1">// Actually, it's more complicated as not every box</span>
        <span class="c1">// can be an observable and we don't restrict that.</span>
        <span class="n">_observable</span><span class="p">.</span><span class="nf">Subscribe</span><span class="p">((</span><span class="n">IObserver</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;)</span> <span class="n">box</span><span class="p">);</span>

        <span class="k">return</span> <span class="n">box</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A box here is nothing than a class holding some state machine in it with maybe some additional details like some interfaces being implemented like <code class="language-plaintext highlighter-rouge">IObservable&lt;T&gt;</code> or <code class="language-plaintext highlighter-rouge">IValueTaskSource&lt;T&gt;</code> and a final state machine that routes handling back to that class. Actually, it’s not implemented as simple as I described, but that’s the idea and it’s simple. Quite enough for now as the focus is on the outer design, on operators.</p>

<p>With just two extension methods the solution is complete. The reason why two methods are needed is that one is for <code class="language-plaintext highlighter-rouge">Operator&lt;TSource&gt;</code> and another one <code class="language-plaintext highlighter-rouge">IObservable&lt;T&gt;</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">OperatorExtensions</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span> <span class="n">Select</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">selector</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="k">new</span> <span class="n">SelectOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;(</span><span class="n">source</span><span class="p">,</span> <span class="n">selector</span><span class="p">);</span>

    <span class="k">public</span> <span class="k">static</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span> <span class="n">Select</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">selector</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="k">new</span> <span class="n">BoxOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;(</span><span class="n">source</span><span class="p">).</span><span class="nf">Select</span><span class="p">(</span><span class="n">selector</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At this point, the goal of making a single box for a continuous observer chain construction has been achieved. No more virtual calls in between, but just one virtual call to notify the box and start the handling process. If there’s thread safety based on locks, then it needs to be done only for the entry point state machine, so that part shall be dirty cheap also. How much? The numbers of a simple benchmark can show that!</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">MemoryDiagnoser</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">HandleChainBenchmarks</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">PublishSubject</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="n">_lightweight</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
    <span class="k">private</span> <span class="n">PublishSubject</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="n">_reactive</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>

    <span class="p">[</span><span class="nf">Params</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="m">5</span><span class="p">,</span> <span class="m">10</span><span class="p">)]</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">ChainLength</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

    <span class="p">[</span><span class="n">GlobalSetup</span><span class="p">]</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Setup</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">lightweight</span> <span class="p">=</span> <span class="n">_lightweight</span><span class="p">.</span><span class="nf">ToOperator</span><span class="p">();</span>
        <span class="kt">var</span> <span class="n">reactive</span> <span class="p">=</span> <span class="n">_reactive</span> <span class="k">as</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;;</span>

        <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="n">ChainLength</span><span class="p">;</span> <span class="n">index</span> <span class="p">+=</span> <span class="m">1</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">lightweight</span> <span class="p">=</span> <span class="n">lightweight</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span> <span class="p">*</span> <span class="m">2</span><span class="p">);</span>
            <span class="n">reactive</span> <span class="p">=</span> <span class="n">System</span><span class="p">.</span><span class="n">Reactive</span><span class="p">.</span><span class="n">Linq</span><span class="p">.</span><span class="n">Observable</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">reactive</span><span class="p">,</span> <span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span> <span class="p">*</span> <span class="m">2</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="n">_</span> <span class="p">=</span> <span class="n">lightweight</span><span class="p">.</span><span class="nf">ToObservable</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Lightweight</span><span class="p">()</span> <span class="p">=&gt;</span>
        <span class="n">_lightweight</span><span class="p">.</span><span class="nf">OnNext</span><span class="p">(</span><span class="m">42</span><span class="p">);</span>

    <span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Reactive</span><span class="p">()</span> <span class="p">=&gt;</span>
        <span class="n">_reactive</span><span class="p">.</span><span class="nf">OnNext</span><span class="p">(</span><span class="m">42</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>ChainLength</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Lightweight</td>
      <td>1</td>
      <td style="text-align: right">6.513 ns</td>
      <td style="text-align: right">1.2197 ns</td>
      <td style="text-align: right">0.0669 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>1</td>
      <td style="text-align: right">1.755 ns</td>
      <td style="text-align: right">0.0589 ns</td>
      <td style="text-align: right">0.0032 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>2</td>
      <td style="text-align: right">7.664 ns</td>
      <td style="text-align: right">0.3312 ns</td>
      <td style="text-align: right">0.0182 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>2</td>
      <td style="text-align: right">1.749 ns</td>
      <td style="text-align: right">0.3682 ns</td>
      <td style="text-align: right">0.0202 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>3</td>
      <td style="text-align: right">9.069 ns</td>
      <td style="text-align: right">1.2868 ns</td>
      <td style="text-align: right">0.0705 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>3</td>
      <td style="text-align: right">1.672 ns</td>
      <td style="text-align: right">0.0013 ns</td>
      <td style="text-align: right">0.0001 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>4</td>
      <td style="text-align: right">9.309 ns</td>
      <td style="text-align: right">0.7826 ns</td>
      <td style="text-align: right">0.0429 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>4</td>
      <td style="text-align: right">1.651 ns</td>
      <td style="text-align: right">0.0284 ns</td>
      <td style="text-align: right">0.0016 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>5</td>
      <td style="text-align: right">10.519 ns</td>
      <td style="text-align: right">0.0778 ns</td>
      <td style="text-align: right">0.0043 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>5</td>
      <td style="text-align: right">1.670 ns</td>
      <td style="text-align: right">0.0429 ns</td>
      <td style="text-align: right">0.0024 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>10</td>
      <td style="text-align: right">17.393 ns</td>
      <td style="text-align: right">0.0629 ns</td>
      <td style="text-align: right">0.0034 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>10</td>
      <td style="text-align: right">1.694 ns</td>
      <td style="text-align: right">0.3569 ns</td>
      <td style="text-align: right">0.0196 ns</td>
      <td style="text-align: right">-</td>
    </tr>
  </tbody>
</table>

<p>And… It seems that Reactive always outperforms the thing we just made. How? Well, the additional execution time comes from the fact that the demonstrated approach diverges from the canonical implementation in .NET where a returned observable isn’t one that actually handles the stream. It only serves as an operator in our case and makes a real handler on subscribe. That’s what should be added to the benchmark to make it correct.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">GlobalSetup</span><span class="p">]</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Setup</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">lightweight</span> <span class="p">=</span> <span class="n">_lightweight</span><span class="p">.</span><span class="nf">ToOperator</span><span class="p">();</span>
    <span class="kt">var</span> <span class="n">reactive</span> <span class="p">=</span> <span class="n">_reactive</span> <span class="k">as</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="n">ChainLength</span><span class="p">;</span> <span class="n">index</span> <span class="p">+=</span> <span class="m">1</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">lightweight</span> <span class="p">=</span> <span class="n">lightweight</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span> <span class="p">*</span> <span class="m">2</span><span class="p">);</span>
        <span class="n">reactive</span> <span class="p">=</span> <span class="n">System</span><span class="p">.</span><span class="n">Reactive</span><span class="p">.</span><span class="n">Linq</span><span class="p">.</span><span class="n">Observable</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">reactive</span><span class="p">,</span> <span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span> <span class="p">*</span> <span class="m">2</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="n">_</span> <span class="p">=</span> <span class="n">lightweight</span><span class="p">.</span><span class="nf">ToObservable</span><span class="p">();</span>
    <span class="n">_</span> <span class="p">=</span> <span class="n">reactive</span><span class="p">.</span><span class="nf">Subscribe</span><span class="p">(</span><span class="n">NoOp</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;.</span><span class="n">Instance</span><span class="p">);</span> <span class="c1">// 👈 that's what we need</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Reactive on purpose does lazy creation of handlers known as sinks. Imagine a situation where you have a replay subject or another one that pushes messages on subscribe. If operators would immediately subscribe to it then they will consume the data before a continuation is connected. That’s why Reactive uses delayed initialization.</p>

<p>We can support that behavior too by packing an operator chain into a fake observable that will build a new state machine and box it on every subscription, that’s not that difficult and makes it compatible with the common implementation. On the other hand instead of calling <code class="language-plaintext highlighter-rouge">ToObservable()</code> one can do <code class="language-plaintext highlighter-rouge">Subscribe(IObserver&lt;T&gt;)</code> where the observer is a subject.</p>

<p>What behavior is the right one? Both, though I think that deferred sink creation should be explicit as it gives more control to the developer and lets them decide what code should exactly do without wasting limited resources.</p>

<p>Now let’s run the fixed benchmark and observe the actual observer materialization costs:</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>ChainLength</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Lightweight</td>
      <td>1</td>
      <td style="text-align: right">6.294 ns</td>
      <td style="text-align: right">1.1997 ns</td>
      <td style="text-align: right">0.0658 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>1</td>
      <td style="text-align: right">6.541 ns</td>
      <td style="text-align: right">1.9919 ns</td>
      <td style="text-align: right">0.1092 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>2</td>
      <td style="text-align: right">7.255 ns</td>
      <td style="text-align: right">0.2327 ns</td>
      <td style="text-align: right">0.0128 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>2</td>
      <td style="text-align: right">9.456 ns</td>
      <td style="text-align: right">1.9292 ns</td>
      <td style="text-align: right">0.1057 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>3</td>
      <td style="text-align: right">9.140 ns</td>
      <td style="text-align: right">2.7408 ns</td>
      <td style="text-align: right">0.1502 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>3</td>
      <td style="text-align: right">11.835 ns</td>
      <td style="text-align: right">0.4100 ns</td>
      <td style="text-align: right">0.0225 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>4</td>
      <td style="text-align: right">10.147 ns</td>
      <td style="text-align: right">1.0510 ns</td>
      <td style="text-align: right">0.0576 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>4</td>
      <td style="text-align: right">14.929 ns</td>
      <td style="text-align: right">0.6923 ns</td>
      <td style="text-align: right">0.0379 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>5</td>
      <td style="text-align: right">11.141 ns</td>
      <td style="text-align: right">1.7854 ns</td>
      <td style="text-align: right">0.0979 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>5</td>
      <td style="text-align: right">16.544 ns</td>
      <td style="text-align: right">0.6192 ns</td>
      <td style="text-align: right">0.0339 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>10</td>
      <td style="text-align: right">19.021 ns</td>
      <td style="text-align: right">2.9314 ns</td>
      <td style="text-align: right">0.1607 ns</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>10</td>
      <td style="text-align: right">28.396 ns</td>
      <td style="text-align: right">1.2660 ns</td>
      <td style="text-align: right">0.0694 ns</td>
      <td style="text-align: right">-</td>
    </tr>
  </tbody>
</table>

<p>It’s better now, really. For a single-operator chain, there’s no difference, but with each additional operator in the chain, it takes less and less time for a value to be handled as no virtual calls are made. Moreover, the JIT compiler can inline operators’ bodies and do more aggressive optimizations if possible. Yet, there’s a question. At which cost it was achieved? To answer it we need one more benchmark:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">MemoryDiagnoser</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">CreateChainBenchmarks</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">PublishSubject</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="n">_observable</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>

    <span class="p">[</span><span class="nf">Params</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="m">5</span><span class="p">,</span> <span class="m">10</span><span class="p">)]</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">ChainLength</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

    <span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
    <span class="k">public</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="nf">Lightweight</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">op</span> <span class="p">=</span> <span class="n">_observable</span><span class="p">.</span><span class="nf">ToOperator</span><span class="p">();</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="n">ChainLength</span><span class="p">;</span> <span class="n">index</span> <span class="p">+=</span> <span class="m">1</span><span class="p">)</span>
            <span class="n">op</span> <span class="p">=</span> <span class="n">op</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">op</span><span class="p">.</span><span class="nf">ToObservable</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
    <span class="k">public</span> <span class="n">IDisposable</span> <span class="nf">Reactive</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">op</span> <span class="p">=</span> <span class="n">_observable</span> <span class="k">as</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;;</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="n">ChainLength</span><span class="p">;</span> <span class="n">index</span> <span class="p">+=</span> <span class="m">1</span><span class="p">)</span>
            <span class="n">op</span> <span class="p">=</span> <span class="n">System</span><span class="p">.</span><span class="n">Reactive</span><span class="p">.</span><span class="n">Linq</span><span class="p">.</span><span class="n">Observable</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">op</span><span class="p">.</span><span class="nf">Subscribe</span><span class="p">(</span><span class="n">NoOp</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;.</span><span class="n">Instance</span><span class="p">);</span>  <span class="c1">// 👈 remember about it</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Before seeing the results let’s remember that the canonical reactive implementation builds real observers only when there’s a subscription. That’s why there’s a no-op observer with empty methods:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">NoOp</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IObserver</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">NoOp</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Instance</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">OnCompleted</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">OnError</span><span class="p">(</span><span class="n">Exception</span> <span class="n">error</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">OnNext</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The numbers now:</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>ChainLength</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Gen0</th>
      <th style="text-align: right">Gen1</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Lightweight</td>
      <td>1</td>
      <td style="text-align: right">404.2 ns</td>
      <td style="text-align: right">123.57 ns</td>
      <td style="text-align: right">6.77 ns</td>
      <td style="text-align: right">0.0253</td>
      <td style="text-align: right">0.0124</td>
      <td style="text-align: right">160 B</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>1</td>
      <td style="text-align: right">379.8 ns</td>
      <td style="text-align: right">84.76 ns</td>
      <td style="text-align: right">4.65 ns</td>
      <td style="text-align: right">0.0229</td>
      <td style="text-align: right">0.0114</td>
      <td style="text-align: right">144 B</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>2</td>
      <td style="text-align: right">402.4 ns</td>
      <td style="text-align: right">84.99 ns</td>
      <td style="text-align: right">4.66 ns</td>
      <td style="text-align: right">0.0315</td>
      <td style="text-align: right">0.0153</td>
      <td style="text-align: right">200 B</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>2</td>
      <td style="text-align: right">554.4 ns</td>
      <td style="text-align: right">62.18 ns</td>
      <td style="text-align: right">3.41 ns</td>
      <td style="text-align: right">0.0343</td>
      <td style="text-align: right">0.0172</td>
      <td style="text-align: right">216 B</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>3</td>
      <td style="text-align: right">443.0 ns</td>
      <td style="text-align: right">55.00 ns</td>
      <td style="text-align: right">3.01 ns</td>
      <td style="text-align: right">0.0381</td>
      <td style="text-align: right">0.0124</td>
      <td style="text-align: right">240 B</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>3</td>
      <td style="text-align: right">645.0 ns</td>
      <td style="text-align: right">43.42 ns</td>
      <td style="text-align: right">2.38 ns</td>
      <td style="text-align: right">0.0458</td>
      <td style="text-align: right">0.0229</td>
      <td style="text-align: right">288 B</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>4</td>
      <td style="text-align: right">492.6 ns</td>
      <td style="text-align: right">62.23 ns</td>
      <td style="text-align: right">3.41 ns</td>
      <td style="text-align: right">0.0439</td>
      <td style="text-align: right">0.0143</td>
      <td style="text-align: right">280 B</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>4</td>
      <td style="text-align: right">801.4 ns</td>
      <td style="text-align: right">172.07 ns</td>
      <td style="text-align: right">9.43 ns</td>
      <td style="text-align: right">0.0572</td>
      <td style="text-align: right">0.0286</td>
      <td style="text-align: right">360 B</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>5</td>
      <td style="text-align: right">572.6 ns</td>
      <td style="text-align: right">197.65 ns</td>
      <td style="text-align: right">10.83 ns</td>
      <td style="text-align: right">0.0505</td>
      <td style="text-align: right">0.0172</td>
      <td style="text-align: right">320 B</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>5</td>
      <td style="text-align: right">922.4 ns</td>
      <td style="text-align: right">294.58 ns</td>
      <td style="text-align: right">16.15 ns</td>
      <td style="text-align: right">0.0687</td>
      <td style="text-align: right">0.0343</td>
      <td style="text-align: right">432 B</td>
    </tr>
    <tr>
      <td>Lightweight</td>
      <td>10</td>
      <td style="text-align: right">954.3 ns</td>
      <td style="text-align: right">311.04 ns</td>
      <td style="text-align: right">17.05 ns</td>
      <td style="text-align: right">0.0820</td>
      <td style="text-align: right">0.0267</td>
      <td style="text-align: right">520 B</td>
    </tr>
    <tr>
      <td>Reactive</td>
      <td>10</td>
      <td style="text-align: right">1,621.0 ns</td>
      <td style="text-align: right">310.53 ns</td>
      <td style="text-align: right">17.02 ns</td>
      <td style="text-align: right">0.1259</td>
      <td style="text-align: right">0.0610</td>
      <td style="text-align: right">792 B</td>
    </tr>
  </tbody>
</table>

<p>Honestly, I’m pretty impressed that the lightweight approach is faster as it does expensive generic virtual calls. At the same time, it does fewer allocations that add some pressure even when they are cheap in .NET. Though, while writing that article I wondered if it could be done even better.</p>

<h1 id="take-three-blend-it">Take three: blend it</h1>

<p>Above there was an attempt to make purely using structs and avoid allocation of factories making observers. There I stated that it’s possible but has huge drawbacks. I’m taking my words back as it turns out that one can make it both extensible and zero-allocating in almost all cases. The exception here comes from the inability of the Roslyn compiler to deduce generic arguments from the outer context as the Rust compiler does.</p>

<p>As zero allocations mean structs the <code class="language-plaintext highlighter-rouge">Operator&lt;T&gt;</code> class turns into an interface:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IOperator</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="n">TBox</span> <span class="n">Box</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">,</span> <span class="n">TBoxFactory</span><span class="p">,</span> <span class="n">TStateMachine</span><span class="p">&gt;(</span><span class="k">in</span> <span class="n">TBoxFactory</span> <span class="n">boxFactory</span><span class="p">,</span> <span class="k">in</span> <span class="n">TStateMachine</span> <span class="n">stateMachine</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TBoxFactory</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachineBoxFactory</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">&gt;</span>
        <span class="k">where</span> <span class="n">TStateMachine</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachine</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The transform aka <code class="language-plaintext highlighter-rouge">Select</code> operator then turns to be a struct:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">readonly</span> <span class="k">struct</span> <span class="nc">Select</span><span class="p">&lt;</span><span class="n">TObservable</span><span class="p">,</span> <span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
    <span class="k">where</span> <span class="n">TObservable</span> <span class="p">:</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">TObservable</span> <span class="n">_observable</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">_selector</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">SelectOp</span><span class="p">(</span><span class="n">TObservable</span> <span class="n">observable</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">selector</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_observable</span> <span class="p">=</span> <span class="n">observable</span><span class="p">;</span>
        <span class="n">_selector</span> <span class="p">=</span> <span class="n">selector</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">TBox</span> <span class="n">Box</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">,</span> <span class="n">TBoxFactory</span><span class="p">,</span> <span class="n">TStateMachine</span><span class="p">&gt;(</span><span class="k">in</span> <span class="n">TBoxFactory</span> <span class="n">boxFactory</span><span class="p">,</span> <span class="k">in</span> <span class="n">TStateMachine</span> <span class="n">stateMachine</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TBoxFactory</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachineBoxFactory</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">&gt;</span>
        <span class="k">where</span> <span class="n">TStateMachine</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachine</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">_observable</span><span class="p">.</span><span class="n">Box</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">,</span> <span class="n">TBoxFactory</span><span class="p">,</span> <span class="n">SelectStateMachine</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">,</span> <span class="n">TStateMachine</span><span class="p">&gt;&gt;(</span>
            <span class="n">boxFactory</span><span class="p">,</span> <span class="k">new</span><span class="p">(</span><span class="n">stateMachine</span><span class="p">,</span> <span class="n">_selector</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">internal</span> <span class="k">struct</span> <span class="nc">SelectStateMachine</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">,</span> <span class="n">TContinuation</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IStateMachine</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span>
    <span class="k">where</span> <span class="n">TContinuation</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachine</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="c1">// as usual and nothing interesting to show again</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Due to <code class="language-plaintext highlighter-rouge">CS0411</code> and the compiler limitation extensions cannot accept structs. That’s why we need to introduce an intermediate struct that will expose all operators and will hold a chain of them:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">readonly</span> <span class="k">struct</span> <span class="nc">Observer</span><span class="p">&lt;</span><span class="n">TOperator</span><span class="p">,</span> <span class="n">T</span><span class="p">&gt;</span>
    <span class="k">where</span> <span class="n">TOperator</span> <span class="p">:</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">TOperator</span> <span class="n">_op</span><span class="p">;</span>

    <span class="k">public</span> <span class="nf">Observer</span><span class="p">(</span><span class="n">TOperator</span> <span class="n">op</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="n">_op</span> <span class="p">=</span> <span class="n">op</span><span class="p">;</span>

    <span class="c1">//     👇 return a wrapper instead of the real operator</span>
    <span class="k">public</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">Select</span><span class="p">&lt;</span><span class="n">TOperator</span><span class="p">,</span> <span class="n">T</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">Select</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">selector</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="k">new</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="n">_op</span><span class="p">,</span> <span class="n">selector</span><span class="p">));</span>

    <span class="c1">// turns the chain into a state machine and boxes it 📦</span>
    <span class="k">public</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="nf">ToObservable</span><span class="p">()</span> <span class="p">=&gt;</span>
        <span class="n">ObservableFactory</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;.</span><span class="nf">Create</span><span class="p">(</span><span class="n">_op</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Instances of that type cannot be used in delegates without knowing the exact operator. The same applies for extensions too. Inheriting <code class="language-plaintext highlighter-rouge">IOperator&lt;T&gt;</code> solves these issues.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">readonly</span> <span class="k">struct</span> <span class="nc">Observer</span><span class="p">&lt;</span><span class="n">TOperator</span><span class="p">,</span> <span class="n">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">TBox</span> <span class="n">Box</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">,</span> <span class="n">TBoxFactory</span><span class="p">,</span> <span class="n">TStateMachine</span><span class="p">&gt;(</span><span class="k">in</span> <span class="n">TBoxFactory</span> <span class="n">boxFactory</span><span class="p">,</span> <span class="k">in</span> <span class="n">TStateMachine</span> <span class="n">stateMachine</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TBoxFactory</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachineBoxFactory</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">&gt;</span>
        <span class="k">where</span> <span class="n">TStateMachine</span> <span class="p">:</span> <span class="k">struct</span><span class="err">,</span> <span class="nc">IStateMachine</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">_op</span><span class="p">.</span><span class="n">Box</span><span class="p">&lt;</span><span class="n">TBox</span><span class="p">,</span> <span class="n">TBoxFactory</span><span class="p">,</span> <span class="n">TStateMachine</span><span class="p">&gt;(</span>
            <span class="n">boxFactory</span><span class="p">,</span> <span class="n">stateMachine</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It leads to duplication of operator-creating methods of the <code class="language-plaintext highlighter-rouge">Operator&lt;TOperator, T&gt;</code> struct as extension methods for <code class="language-plaintext highlighter-rouge">IOperator&lt;T&gt;</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">OperatorExtensions</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">Select</span><span class="p">&lt;</span><span class="n">IOperator</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;,</span> <span class="n">T</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">Select</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">selector</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="k">new</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">selector</span><span class="p">));</span>

    <span class="k">public</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">ToObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">IOperator</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="n">ObservableFactory</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;.</span><span class="nf">Create</span><span class="p">(</span><span class="n">source</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Extensions for <code class="language-plaintext highlighter-rouge">IObservable&lt;T&gt;</code> shall be present too, giving us three methods in total per operator. For third-party extensions, the rule of three is relaxed just to two, only IObservable<T>` and `IOperator<T>` targets are required then.</T></T></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">OperatorExtensions</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Operator</span><span class="p">&lt;</span><span class="n">Select</span><span class="p">&lt;</span><span class="n">Observe</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;,</span> <span class="n">T</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">Select</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">selector</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="k">new</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="n">source</span><span class="p">),</span> <span class="n">selector</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Benchmarks?</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
<span class="k">public</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="nf">CreateChain_1</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">_subject</span>
        <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">ToObservable</span><span class="p">();</span>
<span class="p">}</span>

<span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
<span class="k">public</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="nf">CreateChain_2</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">_subject</span>
        <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">ToObservable</span><span class="p">();</span>
<span class="p">}</span>

<span class="c1">// and so on for chains of 3, 4, 5, and 10 operators...</span>
</code></pre></div></div>

<p>The table below shows that switching to stack allocations and removal of generic virtual calls improves performance by twice. Neat!</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Code Size</th>
      <th style="text-align: right">Gen0</th>
      <th style="text-align: right">Gen1</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>CreateChain_1</td>
      <td style="text-align: right">195.3 ns</td>
      <td style="text-align: right">89.45 ns</td>
      <td style="text-align: right">4.90 ns</td>
      <td style="text-align: right">134 B</td>
      <td style="text-align: right">0.0162</td>
      <td style="text-align: right">0.0081</td>
      <td style="text-align: right">104 B</td>
    </tr>
    <tr>
      <td>CreateChain_2</td>
      <td style="text-align: right">206.6 ns</td>
      <td style="text-align: right">94.17 ns</td>
      <td style="text-align: right">5.16 ns</td>
      <td style="text-align: right">214 B</td>
      <td style="text-align: right">0.0176</td>
      <td style="text-align: right">0.0086</td>
      <td style="text-align: right">112 B</td>
    </tr>
    <tr>
      <td>CreateChain_3</td>
      <td style="text-align: right">240.0 ns</td>
      <td style="text-align: right">12.75 ns</td>
      <td style="text-align: right">0.70 ns</td>
      <td style="text-align: right">236 B</td>
      <td style="text-align: right">0.0191</td>
      <td style="text-align: right">0.0095</td>
      <td style="text-align: right">120 B</td>
    </tr>
    <tr>
      <td>CreateChain_4</td>
      <td style="text-align: right">251.7 ns</td>
      <td style="text-align: right">132.14 ns</td>
      <td style="text-align: right">7.24 ns</td>
      <td style="text-align: right">269 B</td>
      <td style="text-align: right">0.0200</td>
      <td style="text-align: right">0.0100</td>
      <td style="text-align: right">128 B</td>
    </tr>
    <tr>
      <td>CreateChain_5</td>
      <td style="text-align: right">290.6 ns</td>
      <td style="text-align: right">105.83 ns</td>
      <td style="text-align: right">5.80 ns</td>
      <td style="text-align: right">337 B</td>
      <td style="text-align: right">0.0215</td>
      <td style="text-align: right">0.0105</td>
      <td style="text-align: right">136 B</td>
    </tr>
    <tr>
      <td>CreateChain_10</td>
      <td style="text-align: right">566.4 ns</td>
      <td style="text-align: right">168.33 ns</td>
      <td style="text-align: right">9.23 ns</td>
      <td style="text-align: right">906 B</td>
      <td style="text-align: right">0.0277</td>
      <td style="text-align: right">0.0134</td>
      <td style="text-align: right">176 B</td>
    </tr>
  </tbody>
</table>

<h1 id="outro">Outro</h1>

<p>Okay, we did that! But know what? We just made <a href="https://github.com/rxRust/rxRust"><code class="language-plaintext highlighter-rouge">rxRust</code></a> in C#, no jokes. I wasn’t aware of that crate when started poking with the idea of lightweight observers years ago. Yet, the API design is quite the same. I haven’t benchmarked against it and cannot say how close the lightweight observer chains are to what is made in Rust. No doubt they are slower because of delegates and other things that are not supported by .NET and/or C#. It’s unavoidable, unfortunately. Anyway, the lightweight chains were crafted for a project where UI was done with Avalonia for quick prototyping and delivering the product faster, so the goal was to make it in C# and make it as fast as possible with minimal resource usage. Sadly, the project was closed before I had a chance to test what I named <a href="https://github.com/YohDeadfall/Kinetic">Kinetic</a>. I cannot say that it’s production-ready yet as there are some bugs, but hey, that’s open source and you can always open an issue or even contribute by making a pull request! Anything constructive is welcome!</p>]]></content><author><name></name></author><category term="C#" /><category term=".NET" /><category term="observable" /><category term="reactive" /><summary type="html"><![CDATA[Previously I wrote about another approach making properties observable for reactive programming in .NET using the Kinetic project. This time I would like to talk about a non-less interesting feature there, boxing an observer chain into a single-state machine.]]></summary></entry><entry><title type="html">Weakly and strongly typed values in .NET</title><link href="https://yohdeadfall.github.io/weakly-and-strongly-typed-values-in-dotnet" rel="alternate" type="text/html" title="Weakly and strongly typed values in .NET" /><published>2024-05-28T00:00:00+00:00</published><updated>2024-05-28T00:00:00+00:00</updated><id>https://yohdeadfall.github.io/weakly-and-strongly-typed-values-in-dotnet</id><content type="html" xml:base="https://yohdeadfall.github.io/weakly-and-strongly-typed-values-in-dotnet"><![CDATA[<p>Recently I read <a href="https://blog.codingmilitia.com/2024/04/13/primitive-vs-strongly-typed-dictionary-keys-feat-frozen-collections-and-benchmarkdotnet">an interesting article</a> by <a href="https://antunes.dev">João Antunes</a> covering how different type of .NET collections handle strongly and weakly typed keys, but it lacks a very important part, an analysis of why some results are almost identical in some cases and not in others. So, let’s dive in and find answers to these questions.</p>

<!--more-->

<h1 id="preparations">Preparations</h1>

<p>The setup is the same as João used in his work (<a href="https://github.com/joaofbantunes/LooseBenchmarks/tree/main/src/PrimitiveVsStronglyTypedKeyLookup">the source</a>) except a few things:
 * AMD Ryzen 7 4700U instead of Apple M1 silicone,
 * addition of the <code class="language-plaintext highlighter-rouge">[DisassemblyDiagnoser]</code> attribute to get the assembly code generated by the runtime,
 * and a change of the target framework to <code class="language-plaintext highlighter-rouge">net9.0</code> because with <code class="language-plaintext highlighter-rouge">net8.0</code> it crashes on my machine with <code class="language-plaintext highlighter-rouge">SIGSEGV</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">readonly</span> <span class="n">record</span> <span class="k">struct</span> <span class="nc">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">T</span> <span class="n">Value</span><span class="p">);</span>

<span class="p">[</span><span class="n">RankColumn</span><span class="p">]</span>
<span class="p">[</span><span class="n">MemoryDiagnoser</span><span class="p">]</span>
<span class="p">[</span><span class="n">DisassemblyDiagnoser</span><span class="p">]</span>
<span class="p">[</span><span class="nf">GroupBenchmarksBy</span><span class="p">(</span><span class="n">BenchmarkLogicalGroupRule</span><span class="p">.</span><span class="n">ByParams</span><span class="p">,</span> <span class="n">BenchmarkLogicalGroupRule</span><span class="p">.</span><span class="n">ByCategory</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Benchmark</span>
<span class="p">{</span>
<span class="err">  </span><span class="c1">// ...</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">BaseImplementation</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="n">notnull</span>
<span class="p">{</span>
<span class="err">  </span><span class="k">private</span> <span class="k">readonly</span> <span class="n">T</span> <span class="n">_toLookup</span><span class="p">;</span>
<span class="err">  </span><span class="k">private</span> <span class="k">readonly</span> <span class="n">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">_toLookupStronglyTyped</span><span class="p">;</span>
<span class="err"> </span>
<span class="err">  </span><span class="k">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">_traditional</span><span class="p">;</span>
<span class="err">  </span><span class="k">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="n">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">_traditionalWithStronglyTypedKey</span><span class="p">;</span>
<span class="err">  </span><span class="k">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="n">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">_traditionalWithStronglyTypedKeyWithCustomComparer</span><span class="p">;</span>

<span class="err">  </span><span class="k">private</span> <span class="k">readonly</span> <span class="n">FrozenDictionary</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">_frozen</span><span class="p">;</span>
<span class="err">  </span><span class="k">private</span> <span class="k">readonly</span> <span class="n">FrozenDictionary</span><span class="p">&lt;</span><span class="n">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">_frozenWithStronglyTypedKey</span><span class="p">;</span>
<span class="err">  </span><span class="k">private</span> <span class="k">readonly</span> <span class="n">FrozenDictionary</span><span class="p">&lt;</span><span class="n">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">_frozenWithStronglyTypedKeyWithCustomComparer</span><span class="p">;</span>

<span class="err">  </span><span class="k">public</span> <span class="nf">BaseImplementation</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">,</span> <span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="n">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;&gt;</span> <span class="n">equalityComparer</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">T</span><span class="p">&gt;</span> <span class="n">factory</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="kt">var</span> <span class="n">contents</span> <span class="p">=</span> <span class="n">Enumerable</span><span class="p">.</span><span class="nf">Range</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">n</span><span class="p">).</span><span class="nf">Select</span><span class="p">(</span><span class="n">factory</span><span class="p">).</span><span class="nf">ToArray</span><span class="p">();</span>

<span class="err">    </span><span class="n">_toLookup</span> <span class="p">=</span> <span class="n">contents</span><span class="p">.</span><span class="nf">Last</span><span class="p">();</span>
<span class="err">    </span><span class="n">_toLookupStronglyTyped</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">_toLookup</span><span class="p">);</span>

<span class="err">    </span><span class="n">_traditional</span> <span class="p">=</span> <span class="n">contents</span><span class="p">.</span><span class="nf">ToDictionary</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">,</span> <span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">ToString</span><span class="p">()!);</span>
<span class="err">    </span><span class="n">_traditionalWithStronglyTypedKey</span> <span class="p">=</span> <span class="n">contents</span><span class="p">.</span><span class="nf">ToDictionary</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="n">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">x</span><span class="p">),</span> <span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">ToString</span><span class="p">()!);</span>
<span class="err">    </span><span class="n">_traditionalWithStronglyTypedKeyWithCustomComparer</span> <span class="p">=</span> <span class="n">contents</span><span class="p">.</span><span class="nf">ToDictionary</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="n">StronglyTypedKey</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">x</span><span class="p">),</span> <span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">ToString</span><span class="p">()!,</span> <span class="n">equalityComparer</span><span class="p">);</span>

<span class="err">    </span><span class="n">_frozen</span> <span class="p">=</span> <span class="n">_traditional</span><span class="p">.</span><span class="nf">ToFrozenDictionary</span><span class="p">();</span>
<span class="err">    </span><span class="n">_frozenWithStronglyTypedKey</span> <span class="p">=</span> <span class="n">_traditionalWithStronglyTypedKey</span><span class="p">.</span><span class="nf">ToFrozenDictionary</span><span class="p">();</span>
<span class="err">    </span><span class="n">_frozenWithStronglyTypedKeyWithCustomComparer</span> <span class="p">=</span> <span class="n">_traditionalWithStronglyTypedKey</span><span class="p">.</span><span class="nf">ToFrozenDictionary</span><span class="p">(</span><span class="n">equalityComparer</span><span class="p">);</span>
<span class="err">  </span><span class="p">}</span>

<span class="err">  </span><span class="k">public</span> <span class="kt">string</span> <span class="nf">LookupTraditional</span><span class="p">()</span> <span class="p">=&gt;</span>
<span class="err">    </span><span class="n">_traditional</span><span class="p">[</span><span class="n">_toLookup</span><span class="p">];</span>

<span class="err">  </span><span class="k">public</span> <span class="kt">string</span> <span class="nf">LookupTraditionalWithStronglyTypedKey</span><span class="p">()</span> <span class="p">=&gt;</span>
<span class="err">    </span><span class="n">_traditionalWithStronglyTypedKey</span><span class="p">[</span><span class="n">_toLookupStronglyTyped</span><span class="p">];</span>

<span class="err">  </span><span class="k">public</span> <span class="kt">string</span> <span class="nf">LookupTraditionalWithStronglyTypedKeyWithCustomComparer</span><span class="p">()</span> <span class="p">=&gt;</span>
<span class="err">    </span><span class="n">_traditionalWithStronglyTypedKeyWithCustomComparer</span><span class="p">[</span><span class="n">_toLookupStronglyTyped</span><span class="p">];</span>

<span class="err">  </span><span class="k">public</span> <span class="kt">string</span> <span class="nf">LookupFrozen</span><span class="p">()</span> <span class="p">=&gt;</span>
<span class="err">    </span><span class="n">_frozen</span><span class="p">[</span><span class="n">_toLookup</span><span class="p">];</span>

<span class="err">  </span><span class="k">public</span> <span class="kt">string</span> <span class="nf">LookupFrozenWithStronglyTypedKey</span><span class="p">()</span> <span class="p">=&gt;</span>
<span class="err">    </span><span class="n">_frozenWithStronglyTypedKey</span><span class="p">[</span><span class="n">_toLookupStronglyTyped</span><span class="p">];</span>

<span class="err">  </span><span class="k">public</span> <span class="kt">string</span> <span class="nf">LookupFrozenWithStronglyTypedKeyWithCustomComparer</span><span class="p">()</span> <span class="p">=&gt;</span>
<span class="err">    </span><span class="n">_frozenWithStronglyTypedKeyWithCustomComparer</span><span class="p">[</span><span class="n">_toLookupStronglyTyped</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="numbers-time">Numbers time!</h1>

<p>Actually, there are so many numbers that showing all of them is pointless and hell as boring. Therefore, let’s limit the area of discussion by excluding the frozen dictionary and focusing on traditional lookups using <code class="language-plaintext highlighter-rouge">Dictionary&lt;TKey, TValue&gt;</code>.</p>

<p>Still, there are lots of numbers that can be found in the original blog post, I won’t repeat them here and will just mention that there are two cases. In the first one the benchmarks for <code class="language-plaintext highlighter-rouge">T</code> and <code class="language-plaintext highlighter-rouge">StroglyTypedKey&lt;T&gt;</code> give us very close results, and with a custom comparer it’s a bit slower. It happens for <code class="language-plaintext highlighter-rouge">int</code> and <code class="language-plaintext highlighter-rouge">Guid</code>. If <code class="language-plaintext highlighter-rouge">float</code> is used too the picture will be the same. And in the second case, for <code class="language-plaintext highlighter-rouge">string</code>, lookups over <code class="language-plaintext highlighter-rouge">T</code> are faster than over <code class="language-plaintext highlighter-rouge">StroglyTypedKey&lt;T&gt;</code>, and even with a custom comparer over <code class="language-plaintext highlighter-rouge">StroglyTypedKey&lt;T&gt;</code> are better than without it. The difference between these two cases is the kind of <code class="language-plaintext highlighter-rouge">T</code> being used, a value type or a reference type.</p>

<h2 id="generic-keys-over-a-value-type">Generic keys over a value type</h2>

<p>The original article used <code class="language-plaintext highlighter-rouge">int</code> and <code class="language-plaintext highlighter-rouge">Guid</code>, and the picture is the same for both. Therefore, we will continue only with one of these types, with <code class="language-plaintext highlighter-rouge">int</code>.</p>

<table>
  <thead>
    <tr>
      <th>Method                                             </th>
      <th>N     </th>
      <th style="text-align: right">Mean     </th>
      <th style="text-align: right">Error     </th>
      <th style="text-align: right">StdDev    </th>
      <th style="text-align: right">Code Size</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>LookupTraditionalInt                               </td>
      <td>1     </td>
      <td style="text-align: right"> 4.742 ns</td>
      <td style="text-align: right">0.0091 ns</td>
      <td style="text-align: right">0.0085 ns</td>
      <td style="text-align: right">  445 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyInt           </td>
      <td>1     </td>
      <td style="text-align: right"> 4.308 ns</td>
      <td style="text-align: right">0.0069 ns</td>
      <td style="text-align: right">0.0062 ns</td>
      <td style="text-align: right">  438 B</td>
    </tr>
    <tr>
      <td>                                                    </td>
      <td>      </td>
      <td style="text-align: right">     </td>
      <td style="text-align: right">     </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">        </td>
    </tr>
    <tr>
      <td>LookupTraditionalInt                               </td>
      <td>10    </td>
      <td style="text-align: right"> 4.231 ns</td>
      <td style="text-align: right">0.0050 ns</td>
      <td style="text-align: right">0.0047 ns</td>
      <td style="text-align: right">  445 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyInt           </td>
      <td>10    </td>
      <td style="text-align: right"> 4.436 ns</td>
      <td style="text-align: right">0.0058 ns</td>
      <td style="text-align: right">0.0054 ns</td>
      <td style="text-align: right">  438 B</td>
    </tr>
    <tr>
      <td>                                                    </td>
      <td>       </td>
      <td style="text-align: right">          </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">        </td>
    </tr>
    <tr>
      <td>LookupTraditionalInt                               </td>
      <td>100   </td>
      <td style="text-align: right"> 4.858 ns</td>
      <td style="text-align: right">0.0062 ns</td>
      <td style="text-align: right">0.0058 ns</td>
      <td style="text-align: right">  436 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyInt           </td>
      <td>100   </td>
      <td style="text-align: right"> 4.251 ns</td>
      <td style="text-align: right">0.0122 ns</td>
      <td style="text-align: right">0.0114 ns</td>
      <td style="text-align: right">  447 B</td>
    </tr>
    <tr>
      <td>                                                    </td>
      <td>       </td>
      <td style="text-align: right">          </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">     </td>
    </tr>
    <tr>
      <td>LookupTraditionalInt                               </td>
      <td>1000  </td>
      <td style="text-align: right"> 4.951 ns</td>
      <td style="text-align: right">0.0314 ns</td>
      <td style="text-align: right">0.0294 ns</td>
      <td style="text-align: right">  436 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyInt           </td>
      <td>1000  </td>
      <td style="text-align: right"> 4.317 ns</td>
      <td style="text-align: right">0.0440 ns</td>
      <td style="text-align: right">0.0411 ns</td>
      <td style="text-align: right">  447 B</td>
    </tr>
    <tr>
      <td>                                                    </td>
      <td>       </td>
      <td style="text-align: right">     </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">        </td>
    </tr>
    <tr>
      <td>LookupTraditionalInt                               </td>
      <td>10000</td>
      <td style="text-align: right"> 4.749 ns</td>
      <td style="text-align: right">0.0495 ns</td>
      <td style="text-align: right">0.0439 ns</td>
      <td style="text-align: right">  436 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyInt           </td>
      <td>10000</td>
      <td style="text-align: right"> 4.378 ns</td>
      <td style="text-align: right">0.0031 ns</td>
      <td style="text-align: right">0.0027 ns</td>
      <td style="text-align: right">  438 B</td>
    </tr>
  </tbody>
</table>

<p>So, why there’s almost no difference? Well, the earlier addition of <code class="language-plaintext highlighter-rouge">[DisassemblyDiagnoser]</code> was made to answer that question by comparing the assembly code generated by the runtime.</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">-; Benchmark.LookupTraditionalInt()
</span><span class="gi">+; Benchmark.LookupTraditionalWithStronglyTypedKeyInt()
</span><span class="err">    push   rbp</span>
    push   rbx
<span class="err">    push   rax</span>
    lea   rbp,[rsp+10]
<span class="err">    mov   rdi,[rdi+10]</span>
-   mov   rsi,[rdi+8]
<span class="gd">-   mov   ebx,[rdi+38]
</span><span class="gi">+   mov   rsi,[rdi+10]
+   mov   ebx,[rdi+3C]
</span><span class="err">    cmp   [rsi],sil</span>
    mov   rdi,rsi
<span class="err">    mov   esi,ebx</span>
-   call   qword ptr [7E75ED89F540]; System.Collections.Generic.Dictionary`2[[System.Int32, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].FindValue(Int32)
<span class="gi">+   call   qword ptr [70E3098942E8]; System.Collections.Generic.Dictionary`2[[StronglyTypedKey`1[[System.Int32, System.Private.CoreLib]], PrimitiveVsStronglyTypedKeyLookup],[System.__Canon, System.Private.CoreLib]].FindValue(StronglyTypedKey`1&lt;Int32&gt;)
</span><span class="err">    test   rax,rax</span>
    je    short M00_L00
<span class="err">    mov   rax,[rax]</span>
    add   rsp,8
<span class="err">    pop   rbx</span>
    pop   rbp
<span class="err">    ret</span>
 M00_L00:
<span class="err">    mov   edi,ebx</span>
-   call   qword ptr [7E75ED9C6790]
<span class="gi">+   call   qword ptr [70E30996FE28]
</span><span class="err">    int   3</span>
 ; Total bytes of code 57
</code></pre></div></div>

<p>The only difference we can see between these snipped is only the target method names. Nothing interesting to see here.</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">-; System.Collections.Generic.Dictionary`2[[System.Int32, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].FindValue(Int32)
</span><span class="gi">+; System.Collections.Generic.Dictionary`2[[StronglyTypedKey`1[[System.Int32, System.Private.CoreLib]], PrimitiveVsStronglyTypedKeyLookup],[System.__Canon, System.Private.CoreLib]].FindValue(StronglyTypedKey`1&lt;Int32&gt;)
</span><span class="err">    push   rbp</span>
    push   r15
<span class="err">    push   r14</span>
    push   r13
<span class="err">    push   r12</span>
    push   rbx
<span class="err">    sub   rsp,18</span>
    lea   rbp,[rsp+40]
<span class="err">    mov   rbx,rdi</span>
    mov   r15d,esi
<span class="err">    mov   rdi,[rbx+8]</span>
    test   rdi,rdi
<span class="gd">-   je    near ptr M01_L06
</span><span class="gi">+   je    near ptr M01_L03
</span><span class="err">    mov   r14,[rbx+18]</span>
    test   r14,r14
<span class="gd">-   jne   near ptr M01_L03
</span><span class="gi">+   jne   near ptr M01_L04
</span><span class="err">    mov   eax,r15d</span>
    mov   esi,eax
<span class="err">    imul   rsi,[rbx+30]</span>
    shr   rsi,20
<span class="err">    inc   rsi</span>
    mov   r11d,[rdi+8]
<span class="err">    mov   ecx,r11d</span>
    imul   rsi,rcx
<span class="err">    shr   rsi,20</span>
    cmp   esi,r11d
<span class="err">    jae   near</span> ptr M01_L11
<span class="err">    mov   esi,esi</span>
    mov   r13d,[rdi+rsi*4+10]
<span class="err">    mov   r12,[rbx+10]</span>
    xor   ecx,ecx
<span class="err">    dec   r13d</span>
    mov   edx,[r12+8]
<span class="err"> M01_L00:</span>
    cmp   edx,r13d
<span class="gd">-   jbe   near ptr M01_L06
</span><span class="gi">+   jbe   short M01_L03
</span><span class="err">    mov   edi,r13d</span>
    lea   rdi,[rdi+rdi*2]
<span class="err">    lea   r13,[r12+rdi*8+10]</span>
    cmp   [r13+8],r15d
<span class="gd">-   jne   near ptr M01_L07
-   cmp   [r13+10],eax
-   jne   near ptr M01_L07
</span><span class="gi">+   jne   near ptr M01_L08
+   mov   edi,[r13+10]
+   cmp   edi,eax
+   jne   near ptr M01_L08
</span><span class="err"> M01_L01:</span>
    cmp   [r13],r13b
<span class="err">    mov   rax,r13</span>
 M01_L02:
<span class="err">    add   rsp,18</span>
    pop   rbx
<span class="err">    pop   r12</span>
    pop   r13
<span class="err">    pop   r14</span>
    pop   r15
<span class="err">    pop   rbp</span>
    ret
<span class="err"> M01_L03:</span>
+   xor   eax,eax
<span class="gi">+   jmp   short M01_L02
+M01_L04:
</span><span class="err">    mov   rdi,r14</span>
    mov   esi,r15d
<span class="gd">-   mov   r11,7E75EBE70720
</span><span class="gi">+   mov   r11,70E307E40730
</span><span class="err">    call   qword</span> ptr [r11]
<span class="err">    mov   r12d,eax</span>
    mov   rax,[rbx+8]
<span class="err">    mov   esi,r12d</span>
    imul   rsi,[rbx+30]
<span class="err">    shr   rsi,20</span>
    inc   rsi
<span class="err">    mov   edi,[rax+8]</span>
    mov   edx,edi
<span class="err">    imul   rsi,rdx</span>
    shr   rsi,20
<span class="err">    cmp   esi,edi</span>
    jae   near ptr M01_L11
<span class="err">    mov   esi,esi</span>
-   mov   esi,[rax+rsi*4+10]
<span class="gi">+   mov   eax,[rax+rsi*4+10]
</span><span class="err">    mov   rbx,[rbx+10]</span>
    xor   r13d,r13d
<span class="gd">-   dec   esi
-M01_L04:
</span><span class="gi">+   dec   eax
+M01_L05:
</span><span class="err">    mov   ecx,[rbx+8]</span>
-   cmp   ecx,esi
<span class="gd">-   jbe   short M01_L06
-   mov   esi,esi
-   lea   rax,[rsi+rsi*2]
</span><span class="gi">+   cmp   ecx,eax
+   jbe   short M01_L03
+   mov   eax,eax
+   lea   rax,[rax+rax*2]
</span><span class="err">    lea   rax,[rbx+rax*8+10]</span>
    cmp   [rax+8],r12d
<span class="gd">-   je    short M01_L08
-M01_L05:
-   mov   esi,[rax+0C]
</span><span class="gi">+   je    short M01_L09
+M01_L06:
+   mov   eax,[rax+0C]
</span><span class="err">    inc   r13d</span>
    cmp   ecx,r13d
<span class="gd">-   jb    short M01_L10
-   jmp   short M01_L04
-M01_L06:
-   xor   eax,eax
-   jmp   near ptr M01_L02
</span><span class="gi">+   jae   short M01_L05
</span><span class="err"> M01_L07:</span>
+   call   qword ptr [70E308F85278]
<span class="gi">+   int   3
+M01_L08:
</span><span class="err">    mov   r13d,[r13+0C]</span>
    inc   ecx
<span class="err">    cmp   edx,ecx</span>
    jae   near ptr M01_L00
<span class="gd">-   jmp   short M01_L10
-M01_L08:
</span><span class="gi">+   jmp   short M01_L07
+M01_L09:
</span><span class="err">    mov   [rbp-2C],ecx</span>
    mov   [rbp-38],rax
<span class="err">    mov   esi,[rax+10]</span>
    mov   rdi,r14
<span class="err">    mov   edx,r15d</span>
-   mov   r11,7E75EBE70728
<span class="gi">+   mov   r11,70E307E40738
</span><span class="err">    call   qword</span> ptr [r11]
<span class="err">    test   eax,eax</span>
-   jne   short M01_L09
<span class="gi">+   jne   short M01_L10
</span><span class="err">    mov   eax,r13d</span>
    mov   ecx,[rbp-2C]
<span class="err">    mov   r13,[rbp-38]</span>
    mov   edx,eax
<span class="err">    mov   rax,r13</span>
    mov   r13d,edx
<span class="gd">-   jmp   short M01_L05
-M01_L09:
</span><span class="gi">+   jmp   short M01_L06
+M01_L10:
</span><span class="err">    mov   r13,[rbp-38]</span>
    jmp   near ptr M01_L01
<span class="gd">-M01_L10:
-   call   qword ptr [7E75ECFB5278]
-   int   3
</span><span class="err"> M01_L11:</span>
    call   CORINFO_HELP_RNGCHKFAIL
<span class="err">    int   3</span>
-; Total bytes of code 388
<span class="gi">+; Total bytes of code 381
</span></code></pre></div></div>

<p>Now there is more diversity one might say, but even here the outputs are almost identical except for a few minor things:</p>
<ul>
  <li>used registers</li>
  <li>memory addresses of some methods</li>
  <li>location of the <code class="language-plaintext highlighter-rouge">M01_L07</code>/<code class="language-plaintext highlighter-rouge">M01_L10</code> branch which seems to be <code class="language-plaintext highlighter-rouge">ThrowInvalidOperationException_ConcurrentOperationsNotSupported</code>.</li>
</ul>

<p>The source code of <code class="language-plaintext highlighter-rouge">FindValue</code> is identical for both cases and it looks like:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">ref</span> <span class="n">TValue</span> <span class="nf">FindValue</span><span class="p">(</span><span class="n">TKey</span> <span class="n">key</span><span class="p">)</span>
<span class="p">{</span>
<span class="err">  </span><span class="k">ref</span> <span class="n">Entry</span> <span class="n">entry</span> <span class="p">=</span> <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="n">NullRef</span><span class="p">&lt;</span><span class="n">Entry</span><span class="p">&gt;();</span>
<span class="err">  </span><span class="k">if</span> <span class="p">(</span><span class="n">_buckets</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="n">_entries</span> <span class="p">!=</span> <span class="k">null</span><span class="p">,</span> <span class="s">"expected entries to be != null"</span><span class="p">);</span>
<span class="err">    </span><span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;?</span> <span class="n">comparer</span> <span class="p">=</span> <span class="n">_comparer</span><span class="p">;</span>
<span class="err">    </span><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">TKey</span><span class="p">).</span><span class="n">IsValueType</span> <span class="p">&amp;&amp;</span> <span class="c1">// comparer can only be null for value types; enable JIT to eliminate entire if block for ref types</span>
<span class="err">      </span><span class="n">comparer</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
<span class="err">    </span><span class="p">{</span>
<span class="err">      </span><span class="kt">uint</span> <span class="n">hashCode</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">key</span><span class="p">.</span><span class="nf">GetHashCode</span><span class="p">();</span>
<span class="err">      </span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="nf">GetBucket</span><span class="p">(</span><span class="n">hashCode</span><span class="p">);</span>
<span class="err">      </span><span class="n">Entry</span><span class="p">[]?</span> <span class="n">entries</span> <span class="p">=</span> <span class="n">_entries</span><span class="p">;</span>
<span class="err">      </span><span class="kt">uint</span> <span class="n">collisionCount</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>

<span class="err">      </span><span class="c1">// ValueType: Devirtualize with EqualityComparer&lt;TKey&gt;.Default intrinsic</span>
<span class="err">      </span><span class="n">i</span><span class="p">--;</span> <span class="c1">// Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.</span>
<span class="err">      </span><span class="k">do</span>
<span class="err">      </span><span class="p">{</span>
<span class="err">        </span><span class="c1">// Test in if to drop range check for following array access</span>
<span class="err">        </span><span class="k">if</span> <span class="p">((</span><span class="kt">uint</span><span class="p">)</span><span class="n">i</span> <span class="p">&gt;=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">entries</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span>
<span class="err">        </span><span class="p">{</span>
<span class="err">          </span><span class="k">goto</span> <span class="n">ReturnNotFound</span><span class="p">;</span>
<span class="err">        </span><span class="p">}</span>

<span class="err">        </span><span class="n">entry</span> <span class="p">=</span> <span class="k">ref</span> <span class="n">entries</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="err">        </span><span class="k">if</span> <span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">hashCode</span> <span class="p">==</span> <span class="n">hashCode</span> <span class="p">&amp;&amp;</span> <span class="n">EqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;.</span><span class="n">Default</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">key</span><span class="p">,</span> <span class="n">key</span><span class="p">))</span>
<span class="err">        </span><span class="p">{</span>
<span class="err">          </span><span class="k">goto</span> <span class="n">ReturnFound</span><span class="p">;</span>
<span class="err">        </span><span class="p">}</span>

<span class="err">        </span><span class="n">i</span> <span class="p">=</span> <span class="n">entry</span><span class="p">.</span><span class="n">next</span><span class="p">;</span>

<span class="err">        </span><span class="n">collisionCount</span><span class="p">++;</span>
<span class="err">      </span><span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">collisionCount</span> <span class="p">&lt;=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">entries</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
<span class="err">    </span><span class="p">}</span>
<span class="err">    </span><span class="k">else</span>
<span class="err">    </span><span class="p">{</span>
<span class="err">      </span><span class="c1">// ...</span>
<span class="err">    </span><span class="p">}</span>
<span class="err">  </span><span class="p">}</span>

<span class="err">  </span><span class="k">goto</span> <span class="n">ReturnNotFound</span><span class="p">;</span>

<span class="n">ReturnFound</span><span class="p">:</span>
<span class="err">  </span><span class="k">ref</span> <span class="n">TValue</span> <span class="k">value</span> <span class="p">=</span> <span class="k">ref</span> <span class="n">entry</span><span class="p">.</span><span class="k">value</span><span class="p">;</span>
<span class="n">Return</span><span class="p">:</span>
<span class="err">  </span><span class="k">return</span> <span class="k">ref</span> <span class="k">value</span><span class="p">;</span>
<span class="n">ReturnNotFound</span><span class="p">:</span>
<span class="err">  </span><span class="k">value</span> <span class="p">=</span> <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="n">NullRef</span><span class="p">&lt;</span><span class="n">TValue</span><span class="p">&gt;();</span>
<span class="err">  </span><span class="k">goto</span> <span class="n">Return</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The runtime generates a separate method implementation for each generic type if at least one of its generic parameters is a value type, our current case, <code class="language-plaintext highlighter-rouge">TKey</code> is a value type for <code class="language-plaintext highlighter-rouge">int</code> and <code class="language-plaintext highlighter-rouge">StronglyTypedKey&lt;int&gt;</code>. Since there’s a non-shared implementation the runtime attempts to apply another technique named “devirtualization”. It replaces virtual calls with direct invocations if it’s exactly determined. In some cases, it even inlines the body of the callee, and that’s happened for <code class="language-plaintext highlighter-rouge">EqualityComparer&lt;TKey&gt;.Default.Equals</code>.</p>

<p>Now it’s clear why there’s no difference for the observed case without a custom comparer. There is simply no indirect call inside of the lookup method. And when there is a virtual call to <code class="language-plaintext highlighter-rouge">GetHashCode</code> at the beginning of the lookup, and then for each matching bucket the comparer’s <code class="language-plaintext highlighter-rouge">Equals</code> method is also virtually invoked. It takes some additional time and makes the code a bit slower.</p>

<h2 id="generic-keys-over-a-reference-type">Generic keys over a reference type</h2>

<p>After discussing about shared and non-shared generics, and devirtualization no wonder that in the table below we see a different picture for the <code class="language-plaintext highlighter-rouge">string</code> benchmarks. All we know is that the <code class="language-plaintext highlighter-rouge">string</code> type is by-reference even if it has the by-value semantics. The JIT compiler generates an universal implementation for <code class="language-plaintext highlighter-rouge">Dictionary&lt;StronglyTypedKey&lt;string&gt;, string&gt;</code> that can be used for any <code class="language-plaintext highlighter-rouge">Dictionary&lt;StronglyTypedKey&lt;T&gt;, TValue&gt;</code> where <code class="language-plaintext highlighter-rouge">T</code> and <code class="language-plaintext highlighter-rouge">TValue</code> are references.</p>

<table>
  <thead>
    <tr>
      <th>Method                                                        </th>
      <th>N  </th>
      <th style="text-align: right">Mean      </th>
      <th style="text-align: right">Error  </th>
      <th style="text-align: right">StdDev  </th>
      <th style="text-align: right">Code Size</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>LookupTraditionalString                   </td>
      <td>1     </td>
      <td style="text-align: right">19.302 ns</td>
      <td style="text-align: right">0.0387 ns</td>
      <td style="text-align: right">0.0362 ns</td>
      <td style="text-align: right">  401 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyString         </td>
      <td>1     </td>
      <td style="text-align: right">38.275 ns</td>
      <td style="text-align: right">0.0404 ns</td>
      <td style="text-align: right">0.0378 ns</td>
      <td style="text-align: right">  792 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyStringWithCustomComparer</td>
      <td>1     </td>
      <td style="text-align: right">31.926 ns</td>
      <td style="text-align: right">0.0499 ns</td>
      <td style="text-align: right">0.0467 ns</td>
      <td style="text-align: right">  614 B</td>
    </tr>
    <tr>
      <td>                               </td>
      <td>       </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">     </td>
      <td style="text-align: right">     </td>
    </tr>
    <tr>
      <td>LookupTraditionalString                   </td>
      <td>10    </td>
      <td style="text-align: right">16.565 ns</td>
      <td style="text-align: right">0.0393 ns</td>
      <td style="text-align: right">0.0368 ns</td>
      <td style="text-align: right">  401 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyString         </td>
      <td>10    </td>
      <td style="text-align: right">37.746 ns</td>
      <td style="text-align: right">0.0299 ns</td>
      <td style="text-align: right">0.0280 ns</td>
      <td style="text-align: right">  792 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyStringWithCustomComparer</td>
      <td>10    </td>
      <td style="text-align: right">32.230 ns</td>
      <td style="text-align: right">0.3138 ns</td>
      <td style="text-align: right">0.2935 ns</td>
      <td style="text-align: right">  614 B</td>
    </tr>
    <tr>
      <td>                               </td>
      <td>       </td>
      <td style="text-align: right">     </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">    </td>
      <td style="text-align: right">     </td>
    </tr>
    <tr>
      <td>LookupTraditionalString                   </td>
      <td>100   </td>
      <td style="text-align: right">17.792 ns</td>
      <td style="text-align: right">0.0362 ns</td>
      <td style="text-align: right">0.0339 ns</td>
      <td style="text-align: right">  410 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyString         </td>
      <td>100   </td>
      <td style="text-align: right">38.961 ns</td>
      <td style="text-align: right">0.0492 ns</td>
      <td style="text-align: right">0.0460 ns</td>
      <td style="text-align: right">  792 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyStringWithCustomComparer</td>
      <td>100   </td>
      <td style="text-align: right">31.624 ns</td>
      <td style="text-align: right">0.1461 ns</td>
      <td style="text-align: right">0.1220 ns</td>
      <td style="text-align: right">  614 B</td>
    </tr>
    <tr>
      <td>                               </td>
      <td>       </td>
      <td style="text-align: right">     </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">        </td>
    </tr>
    <tr>
      <td>LookupTraditionalString                   </td>
      <td>1000  </td>
      <td style="text-align: right">20.657 ns</td>
      <td style="text-align: right">0.1705 ns</td>
      <td style="text-align: right">0.1511 ns</td>
      <td style="text-align: right">  410 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyString                   </td>
      <td>1000  </td>
      <td style="text-align: right">40.340 ns</td>
      <td style="text-align: right">0.0433 ns</td>
      <td style="text-align: right">0.0405 ns</td>
      <td style="text-align: right">  792 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyStringWithCustomComparer</td>
      <td>1000  </td>
      <td style="text-align: right">32.797 ns</td>
      <td style="text-align: right">0.1520 ns</td>
      <td style="text-align: right">0.1422 ns</td>
      <td style="text-align: right">  614 B</td>
    </tr>
    <tr>
      <td>                                                               </td>
      <td>       </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">           </td>
      <td style="text-align: right">        </td>
    </tr>
    <tr>
      <td>LookupTraditionalString                                       </td>
      <td>10000</td>
      <td style="text-align: right">18.818 ns</td>
      <td style="text-align: right">0.0832 ns</td>
      <td style="text-align: right">0.0778 ns</td>
      <td style="text-align: right">  410 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyString                   </td>
      <td>10000</td>
      <td style="text-align: right">39.701 ns</td>
      <td style="text-align: right">0.0759 ns</td>
      <td style="text-align: right">0.0710 ns</td>
      <td style="text-align: right">  792 B</td>
    </tr>
    <tr>
      <td>LookupTraditionalWithStronglyTypedKeyStringWithCustomComparer</td>
      <td>10000</td>
      <td style="text-align: right">32.882 ns</td>
      <td style="text-align: right">0.0286 ns</td>
      <td style="text-align: right">0.0253 ns</td>
      <td style="text-align: right">  620 B</td>
    </tr>
  </tbody>
</table>

<p>In any produced assembly a shared generic type can be spotted by usage of the <code class="language-plaintext highlighter-rouge">__Canon</code> type instead of the actual generic parameters. That’s exactly what we have for the strongly typed generic keys when <code class="language-plaintext highlighter-rouge">T</code> is <code class="language-plaintext highlighter-rouge">string</code>:</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">; Benchmark.LookupTraditionalWithStronglyTypedKeyString()</span>
<span class="err">    </span><span class="nf">push</span><span class="err">   </span><span class="nb">rbp</span>
<span class="err">    </span><span class="nf">push</span><span class="err">   </span><span class="nb">rbx</span>
<span class="err">    </span><span class="nf">push</span><span class="err">   </span><span class="nb">rax</span>
<span class="err">    </span><span class="nf">lea</span><span class="err">   </span><span class="nb">rbp</span><span class="p">,[</span><span class="nb">rsp</span><span class="o">+</span><span class="mi">10</span><span class="p">]</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rdi</span><span class="p">,[</span><span class="nb">rdi</span><span class="o">+</span><span class="mi">8</span><span class="p">]</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rsi</span><span class="p">,[</span><span class="nb">rdi</span><span class="o">+</span><span class="mi">18</span><span class="p">]</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rbx</span><span class="p">,[</span><span class="nb">rdi</span><span class="o">+</span><span class="mi">28</span><span class="p">]</span>
<span class="err">    </span><span class="nf">cmp</span><span class="err">   </span><span class="p">[</span><span class="nb">rsi</span><span class="p">],</span><span class="nb">si</span><span class="nv">l</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rdi</span><span class="p">,</span><span class="nb">rsi</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rsi</span><span class="p">,</span><span class="nb">rbx</span>
<span class="err">    </span><span class="nf">call</span><span class="err">   </span><span class="kt">qword</span> <span class="nv">ptr</span> <span class="p">[</span><span class="mi">76939</span><span class="nv">B3677C8</span><span class="p">]</span><span class="c1">; System.Collections.Generic.Dictionary`2[[StronglyTypedKey`1[[System.__Canon, System.Private.CoreLib]], PrimitiveVsStronglyTypedKeyLookup],[System.__Canon, System.Private.CoreLib]].FindValue(StronglyTypedKey`1&lt;System.__Canon&gt;)</span>
<span class="err">    </span><span class="nf">test</span><span class="err">   </span><span class="nb">rax</span><span class="p">,</span><span class="nb">rax</span>
<span class="err">    </span><span class="nf">je</span><span class="err">    </span><span class="nv">short</span> <span class="nv">M00_L00</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rax</span><span class="p">,[</span><span class="nb">rax</span><span class="p">]</span>
<span class="err">    </span><span class="nf">add</span><span class="err">   </span><span class="nb">rsp</span><span class="p">,</span><span class="mi">8</span>
<span class="err">    </span><span class="nf">pop</span><span class="err">   </span><span class="nb">rbx</span>
<span class="err">    </span><span class="nf">pop</span><span class="err">   </span><span class="nb">rbp</span>
<span class="err">    </span><span class="nf">ret</span>
<span class="nl">M00_L00:</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rsi</span><span class="p">,</span><span class="nb">rbx</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rdi</span><span class="p">,</span><span class="nv">offset</span> <span class="nv">MD_System.ThrowHelper.ThrowKeyNotFoundException</span><span class="p">[[</span><span class="nv">StronglyTypedKey</span><span class="err">`</span><span class="mi">1</span><span class="p">[[</span><span class="nv">System.String</span><span class="p">,</span> <span class="nv">System.Private.CoreLib</span><span class="p">]],</span> <span class="nv">PrimitiveVsStronglyTypedKeyLookup</span><span class="p">]](</span><span class="nv">StronglyTypedKey</span><span class="err">`</span><span class="mi">1</span><span class="o">&lt;</span><span class="nv">System.String</span><span class="o">&gt;</span><span class="p">)</span>
<span class="err">    </span><span class="nf">call</span><span class="err">   </span><span class="kt">qword</span> <span class="nv">ptr</span> <span class="p">[</span><span class="mi">76939</span><span class="nv">B46FBA0</span><span class="p">]</span>
<span class="err">    </span><span class="nf">int</span><span class="err">   </span><span class="mi">3</span>
<span class="c1">; Total bytes of code 70</span>
</code></pre></div></div>

<p>Expectedly a shared code might be slower than a non-shared one, but there’s a good reason for having sharing in general. Since any reference type is a pointer under the hood to the corresponding object on the heap, most of the code is the same. No need to waste storage and RAM on duplicating pieces when they can be collapsed into a single block. If there’s a type dependant branching it’s always possible to obtain the type handle from the object or a special register for generic methods. Still, it doesn’t answer why a custom comparer boosts the execution.</p>

<h2 id="comparision-magic">Comparision magic</h2>

<p>To understand why the usage of a custom comparer outperforms the case when it’s not provided, the <code class="language-plaintext highlighter-rouge">Dictionary&lt;TKey, TValue&gt;</code>’s code should be checked.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">Dictionary</span><span class="p">(</span><span class="kt">int</span> <span class="n">capacity</span><span class="p">,</span> <span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;?</span> <span class="n">comparer</span><span class="p">)</span>
<span class="p">{</span>
<span class="err">  </span><span class="k">if</span> <span class="p">(</span><span class="n">capacity</span> <span class="p">&lt;</span> <span class="m">0</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="n">ThrowHelper</span><span class="p">.</span><span class="nf">ThrowArgumentOutOfRangeException</span><span class="p">(</span><span class="n">ExceptionArgument</span><span class="p">.</span><span class="n">capacity</span><span class="p">);</span>
<span class="err">  </span><span class="p">}</span>

<span class="err">  </span><span class="k">if</span> <span class="p">(</span><span class="n">capacity</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="nf">Initialize</span><span class="p">(</span><span class="n">capacity</span><span class="p">);</span>
<span class="err">  </span><span class="p">}</span>

<span class="err">  </span><span class="c1">// For reference types, we always want to store a comparer instance, either</span>
<span class="err">  </span><span class="c1">// the one provided, or if one wasn't provided, the default (accessing</span>
<span class="err">  </span><span class="c1">// EqualityComparer&lt;TKey&gt;.Default with shared generics on every dictionary</span>
<span class="err">  </span><span class="c1">// access can add measurable overhead). For value types, if no comparer is</span>
<span class="err">  </span><span class="c1">// provided, or if the default is provided, we'd prefer to use</span>
<span class="err">  </span><span class="c1">// EqualityComparer&lt;TKey&gt;.Default.Equals on every use, enabling the JIT to</span>
<span class="err">  </span><span class="c1">// devirtualize and possibly inline the operation.</span>
<span class="err">  </span><span class="k">if</span> <span class="p">(!</span><span class="k">typeof</span><span class="p">(</span><span class="n">TKey</span><span class="p">).</span><span class="n">IsValueType</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="n">_comparer</span> <span class="p">=</span> <span class="n">comparer</span> <span class="p">??</span> <span class="n">EqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;.</span><span class="n">Default</span><span class="p">;</span>

<span class="err">    </span><span class="c1">// Special-case EqualityComparer&lt;string&gt;.Default, StringComparer.Ordinal, and StringComparer.OrdinalIgnoreCase.</span>
<span class="err">    </span><span class="c1">// We use a non-randomized comparer for improved perf, falling back to a randomized comparer if the</span>
<span class="err">    </span><span class="c1">// hash buckets become unbalanced.</span>
<span class="err">    </span><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">TKey</span><span class="p">)</span> <span class="p">==</span> <span class="k">typeof</span><span class="p">(</span><span class="kt">string</span><span class="p">)</span> <span class="p">&amp;&amp;</span>
<span class="err">      </span><span class="n">NonRandomizedStringEqualityComparer</span><span class="p">.</span><span class="nf">GetStringComparer</span><span class="p">(</span><span class="n">_comparer</span><span class="p">!)</span> <span class="k">is</span> <span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">stringComparer</span><span class="p">)</span>
<span class="err">    </span><span class="p">{</span>
<span class="err">      </span><span class="n">_comparer</span> <span class="p">=</span> <span class="p">(</span><span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;)</span><span class="n">stringComparer</span><span class="p">;</span>
<span class="err">    </span><span class="p">}</span>
<span class="err">  </span><span class="p">}</span>
<span class="err">  </span><span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">comparer</span> <span class="k">is</span> <span class="n">not</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="c1">// first check for null to avoid forcing default comparer instantiation unnecessarily</span>
<span class="err">       </span><span class="n">comparer</span> <span class="p">!=</span> <span class="n">EqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;.</span><span class="n">Default</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="n">_comparer</span> <span class="p">=</span> <span class="n">comparer</span><span class="p">;</span>
<span class="err">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Our wrapper is a value type, so the last <code class="language-plaintext highlighter-rouge">if</code> block applies. And indeed, the <code class="language-plaintext highlighter-rouge">_comparer</code> is set only when provided to the constructor and if it’s not the default one in case of a value type. Therefore, we have no comparer assigned, but something shall be used instead, right? That’s exactly what we have seen, <code class="language-plaintext highlighter-rouge">EqualityComparer&lt;TKey&gt;.Default.Equals</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">ref</span> <span class="n">TValue</span> <span class="nf">FindValue</span><span class="p">(</span><span class="n">TKey</span> <span class="n">key</span><span class="p">)</span>
<span class="p">{</span>
<span class="err">  </span><span class="c1">// ...</span>

<span class="err">  </span><span class="k">ref</span> <span class="n">Entry</span> <span class="n">entry</span> <span class="p">=</span> <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="n">NullRef</span><span class="p">&lt;</span><span class="n">Entry</span><span class="p">&gt;();</span>
<span class="err">  </span><span class="k">if</span> <span class="p">(</span><span class="n">_buckets</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="n">_entries</span> <span class="p">!=</span> <span class="k">null</span><span class="p">,</span> <span class="s">"expected entries to be != null"</span><span class="p">);</span>
<span class="err">    </span><span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;?</span> <span class="n">comparer</span> <span class="p">=</span> <span class="n">_comparer</span><span class="p">;</span>
<span class="err">    </span><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">TKey</span><span class="p">).</span><span class="n">IsValueType</span> <span class="p">&amp;&amp;</span> <span class="c1">// comparer can only be null for value types; enable JIT to eliminate entire if block for ref types</span>
<span class="err">      </span><span class="n">comparer</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
<span class="err">    </span><span class="p">{</span>
<span class="err">      </span><span class="c1">// ..</span>
<span class="err">      </span><span class="k">do</span>
<span class="err">      </span><span class="p">{</span>
<span class="err">        </span><span class="c1">// ..</span>
<span class="err">        </span><span class="c1">// Resolution of the default equality comparer 👇</span>
<span class="err">        </span><span class="k">if</span> <span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">hashCode</span> <span class="p">==</span> <span class="n">hashCode</span> <span class="p">&amp;&amp;</span> <span class="n">EqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;.</span><span class="n">Default</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">key</span><span class="p">,</span> <span class="n">key</span><span class="p">))</span>
<span class="err">        </span><span class="p">{</span>
<span class="err">          </span><span class="k">goto</span> <span class="n">ReturnFound</span><span class="p">;</span>
<span class="err">        </span><span class="p">}</span>

<span class="err">        </span><span class="c1">// ..</span>
<span class="err">      </span><span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">collisionCount</span> <span class="p">&lt;=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">entries</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
<span class="err">    </span><span class="p">}</span>
<span class="err">    </span><span class="k">else</span>
<span class="err">    </span><span class="p">{</span>
<span class="err">      </span><span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="n">comparer</span> <span class="k">is</span> <span class="n">not</span> <span class="k">null</span><span class="p">);</span>
<span class="err">      </span><span class="c1">// ..</span>
<span class="err">      </span><span class="k">do</span>
<span class="err">      </span><span class="p">{</span>
<span class="err">        </span><span class="c1">// ..</span>
<span class="err">        </span><span class="c1">// Just a virtual call, nothing else 👇</span>
<span class="err">        </span><span class="k">if</span> <span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">hashCode</span> <span class="p">==</span> <span class="n">hashCode</span> <span class="p">&amp;&amp;</span> <span class="n">comparer</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">key</span><span class="p">,</span> <span class="n">key</span><span class="p">))</span>
<span class="err">        </span><span class="p">{</span>
<span class="err">          </span><span class="k">goto</span> <span class="n">ReturnFound</span><span class="p">;</span>
<span class="err">        </span><span class="p">}</span>

<span class="err">        </span><span class="c1">// ..</span>
<span class="err">      </span><span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">collisionCount</span> <span class="p">&lt;=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">entries</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
<span class="err">    </span><span class="p">}</span>
<span class="err">  </span><span class="c1">// ..</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Yeah, babe, the default key comparer is resolved each time when there’s a matching hash code even if it’s a collision. It’s done that way for a very good reason we already discussed, devirtualization. The JIT compiler can only spot a possible optimization site by the full match, i.e. exactly <code class="language-plaintext highlighter-rouge">EqualityComparer&lt;T&gt;.Default.Equals</code>. Otherwise, if the comparer is stored in a variable in the same method and used later, the optimization cannot be applied. It even works in our current case:</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">; System.Collections.Generic.Dictionary`2[[StronglyTypedKey`1[[System.__Canon, System.Private.CoreLib]], PrimitiveVsStronglyTypedKeyLookup],[System.__Canon, System.Private.CoreLib]].FindValue(StronglyTypedKey`1&lt;System.__Canon&gt;)</span>
<span class="err">    </span><span class="c1">; ...</span>
<span class="nl">M01_L02:</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rdi</span><span class="p">,</span><span class="nb">rsi</span>
<span class="err">    </span><span class="nf">call</span><span class="err">   </span><span class="kt">qword</span> <span class="nv">ptr</span> <span class="p">[</span><span class="mi">76939</span><span class="nv">B36CD08</span><span class="p">]</span><span class="c1">; System.Collections.Generic.EqualityComparer`1[[StronglyTypedKey`1[[System.__Canon, System.Private.CoreLib]], PrimitiveVsStronglyTypedKeyLookup]].get_Default()</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rdi</span><span class="p">,</span><span class="nb">rax</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rcx</span><span class="p">,[</span><span class="nv">r14</span><span class="o">+</span><span class="mi">10</span><span class="p">]</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rax</span><span class="p">,</span><span class="nv">offset</span> <span class="nv">MT_System.Collections.Generic.GenericEqualityComparer</span><span class="err">`</span><span class="mi">1</span><span class="p">[[</span><span class="nv">StronglyTypedKey</span><span class="err">`</span><span class="mi">1</span><span class="p">[[</span><span class="nv">System.String</span><span class="p">,</span> <span class="nv">System.Private.CoreLib</span><span class="p">]],</span> <span class="nv">PrimitiveVsStronglyTypedKeyLookup</span><span class="p">]]</span>
<span class="err">    </span><span class="nf">cmp</span><span class="err">   </span><span class="p">[</span><span class="nb">rdi</span><span class="p">],</span><span class="nb">rax</span>
<span class="err">    </span><span class="nf">jne</span><span class="err">   </span><span class="nv">near</span> <span class="nv">ptr</span> <span class="nv">M01_L17</span>
<span class="err">    </span><span class="c1">; Inlined body of "StronglyTypedKey&lt;T&gt;.Equals" which contains inlined "string.Equals"</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">rsi</span><span class="p">,[</span><span class="nb">rbp</span><span class="o">-</span><span class="mi">38</span><span class="p">]</span>
<span class="err">    </span><span class="nf">test</span><span class="err">   </span><span class="nb">rcx</span><span class="p">,</span><span class="nb">rcx</span>
<span class="err">    </span><span class="nf">je</span><span class="err">    </span><span class="nv">near</span> <span class="nv">ptr</span> <span class="nv">M01_L16</span>
<span class="err">    </span><span class="nf">test</span><span class="err">   </span><span class="nb">rsi</span><span class="p">,</span><span class="nb">rsi</span>
<span class="err">    </span><span class="nf">je</span><span class="err">    </span><span class="nv">near</span> <span class="nv">ptr</span> <span class="nv">M01_L15</span>
<span class="err">    </span><span class="nf">cmp</span><span class="err">   </span><span class="nb">rcx</span><span class="p">,</span><span class="nb">rsi</span>
<span class="err">    </span><span class="nf">jne</span><span class="err">   </span><span class="nv">near</span> <span class="nv">ptr</span> <span class="nv">M01_L14</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">r8d</span><span class="p">,</span><span class="mi">1</span>
<span class="nl">M01_L03:</span>
<span class="err">    </span><span class="nf">test</span><span class="err">   </span><span class="nb">r8d</span><span class="p">,</span><span class="nb">r8d</span>
<span class="err">    </span><span class="nf">je</span><span class="err">    </span><span class="nv">short</span> <span class="nv">M01_L12</span>
<span class="err">    </span><span class="c1">; ...</span>
<span class="nl">M01_L14:</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">edx</span><span class="p">,[</span><span class="nb">rcx</span><span class="o">+</span><span class="mi">8</span><span class="p">]</span>
<span class="err">    </span><span class="nf">cmp</span><span class="err">   </span><span class="nb">edx</span><span class="p">,[</span><span class="nb">rsi</span><span class="o">+</span><span class="mi">8</span><span class="p">]</span>
<span class="err">    </span><span class="nf">jne</span><span class="err">   </span><span class="nv">short</span> <span class="nv">M01_L13</span>
<span class="err">    </span><span class="nf">lea</span><span class="err">   </span><span class="nb">rdi</span><span class="p">,[</span><span class="nb">rcx</span><span class="o">+</span><span class="mi">0</span><span class="nv">C</span><span class="p">]</span>
<span class="err">    </span><span class="nf">add</span><span class="err">   </span><span class="nb">rsi</span><span class="p">,</span><span class="mi">0</span><span class="nv">C</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">edx</span><span class="p">,[</span><span class="nb">rcx</span><span class="o">+</span><span class="mi">8</span><span class="p">]</span>
<span class="err">    </span><span class="nf">add</span><span class="err">   </span><span class="nb">edx</span><span class="p">,</span><span class="nb">edx</span>
<span class="err">    </span><span class="nf">call</span><span class="err">   </span><span class="kt">qword</span> <span class="nv">ptr</span> <span class="p">[</span><span class="mi">76939</span><span class="nv">A724B58</span><span class="p">]</span><span class="c1">; System.SpanHelpers.SequenceEqual(Byte ByRef, Byte ByRef, UIntPtr)</span>
<span class="err">    </span><span class="nf">mov</span><span class="err">   </span><span class="nb">r8d</span><span class="p">,</span><span class="nb">eax</span>
<span class="err">    </span><span class="nf">jmp</span><span class="err">   </span><span class="nv">near</span> <span class="nv">ptr</span> <span class="nv">M01_L03</span>
<span class="err">    </span><span class="c1">; ...</span>
</code></pre></div></div>

<p>It fetches the default comparer first and then checks if it’s a cached instance of <code class="language-plaintext highlighter-rouge">GenericEqualityComparer&lt;StronglyTypedKey&lt;string&gt;&gt;</code>. If so, then it uses the devirtualized equality method of <code class="language-plaintext highlighter-rouge">StronglyTypedKey&lt;string&gt;</code> which in turn is a simple string equality inlined too.</p>

<p>The problem here is that for shared generics the default comparer has to be retrieved each time and it isn’t cheap. Perhaps, if there would be a method similar to <code class="language-plaintext highlighter-rouge">RuntimeHelpers.IsReferenceOrContainsReferences&lt;T&gt;</code>, but which would tell us if <code class="language-plaintext highlighter-rouge">T</code> is a shared generic or not, the dictionary’s constructor can be changed in such a way that for shared generics it will set the comparer like for reference types.</p>

<h2 id="its-not-over-yet">It’s not over yet!</h2>

<p>Do you think that the troubles came to an end? No, there’s one more reason for the strongly typed key over <code class="language-plaintext highlighter-rouge">string</code> benchmark to be slow. Earlier I showed you the dictionary’s constructor which has a specially crafted logic when the key is a type of <code class="language-plaintext highlighter-rouge">string</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">Dictionary</span><span class="p">(</span><span class="kt">int</span> <span class="n">capacity</span><span class="p">,</span> <span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;?</span> <span class="n">comparer</span><span class="p">)</span>
<span class="p">{</span>
<span class="err">  </span><span class="c1">// ..</span>

<span class="err">  </span><span class="k">if</span> <span class="p">(!</span><span class="k">typeof</span><span class="p">(</span><span class="n">TKey</span><span class="p">).</span><span class="n">IsValueType</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="n">_comparer</span> <span class="p">=</span> <span class="n">comparer</span> <span class="p">??</span> <span class="n">EqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;.</span><span class="n">Default</span><span class="p">;</span>

<span class="err">    </span><span class="c1">// Special-case EqualityComparer&lt;string&gt;.Default, StringComparer.Ordinal, and StringComparer.OrdinalIgnoreCase.</span>
<span class="err">    </span><span class="c1">// We use a non-randomized comparer for improved perf, falling back to a randomized comparer if the</span>
<span class="err">    </span><span class="c1">// hash buckets become unbalanced.</span>
<span class="err">    </span><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">TKey</span><span class="p">)</span> <span class="p">==</span> <span class="k">typeof</span><span class="p">(</span><span class="kt">string</span><span class="p">)</span> <span class="p">&amp;&amp;</span>
<span class="err">      </span><span class="n">NonRandomizedStringEqualityComparer</span><span class="p">.</span><span class="nf">GetStringComparer</span><span class="p">(</span><span class="n">_comparer</span><span class="p">!)</span> <span class="k">is</span> <span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">stringComparer</span><span class="p">)</span>
<span class="err">    </span><span class="p">{</span>
<span class="err">      </span><span class="n">_comparer</span> <span class="p">=</span> <span class="p">(</span><span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;)</span><span class="n">stringComparer</span><span class="p">;</span>
<span class="err">    </span><span class="p">}</span>

<span class="err">    </span><span class="c1">//..</span>
<span class="err">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here it is, <code class="language-plaintext highlighter-rouge">NonRandomizedStringEqualityComparer</code>. a special equality comparer which instead of <code class="language-plaintext highlighter-rouge">GetHashCode</code> invocations on a string calls <code class="language-plaintext highlighter-rouge">GetNonRandomizedHashCode</code>. Yeah, I know, it feels very suspicious. While one might expect that <code class="language-plaintext highlighter-rouge">GetHashCode</code> on a string produces a stable value only depending on its content, it’s not the case. It also uses a different algorithm, Marvin hash, and a random seed generated only once every time the application runs. The whole point of that thing is the prevention of hash flooding attacks, and since it’s about huge amounts of data the protection doesn’t apply for small collections when collision probabilities are low and equality operations aren’t expensive, i.e. till the collection reaches some threshold. After that, the .NET switches to a randomized equality comparer and rehashes all entries in the collection.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="kt">bool</span> <span class="nf">TryInsert</span><span class="p">(</span><span class="n">TKey</span> <span class="n">key</span><span class="p">,</span> <span class="n">TValue</span> <span class="k">value</span><span class="p">,</span> <span class="n">InsertionBehavior</span> <span class="n">behavior</span><span class="p">)</span>
<span class="p">{</span>
<span class="err">  </span><span class="c1">// ...</span>
<span class="err">  </span><span class="c1">// Value types never rehash</span>
<span class="err">  </span><span class="k">if</span> <span class="p">(!</span><span class="k">typeof</span><span class="p">(</span><span class="n">TKey</span><span class="p">).</span><span class="n">IsValueType</span> <span class="p">&amp;&amp;</span> <span class="n">collisionCount</span> <span class="p">&gt;</span> <span class="n">HashHelpers</span><span class="p">.</span><span class="n">HashCollisionThreshold</span> <span class="p">&amp;&amp;</span> <span class="n">comparer</span> <span class="k">is</span> <span class="n">NonRandomizedStringEqualityComparer</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="c1">// If we hit the collision threshold we'll need to switch to the comparer which uses randomized string hashing</span>
<span class="err">    </span><span class="c1">// i.e. EqualityComparer&lt;string&gt;.Default.</span>
<span class="err">    </span><span class="nf">Resize</span><span class="p">(</span><span class="n">entries</span><span class="p">.</span><span class="n">Length</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="err">  </span><span class="p">}</span>
<span class="err">  </span><span class="c1">// ...</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">void</span> <span class="nf">Resize</span><span class="p">(</span><span class="kt">int</span> <span class="n">newSize</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">forceNewHashCodes</span><span class="p">)</span>
<span class="p">{</span>
<span class="err">  </span><span class="c1">// Value types never rehash</span>
<span class="err">  </span><span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(!</span><span class="n">forceNewHashCodes</span> <span class="p">||</span> <span class="p">!</span><span class="k">typeof</span><span class="p">(</span><span class="n">TKey</span><span class="p">).</span><span class="n">IsValueType</span><span class="p">);</span>
<span class="err">  </span><span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="n">_entries</span> <span class="p">!=</span> <span class="k">null</span><span class="p">,</span> <span class="s">"_entries should be non-null"</span><span class="p">);</span>
<span class="err">  </span><span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="n">newSize</span> <span class="p">&gt;=</span> <span class="n">_entries</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>

<span class="err">  </span><span class="n">Entry</span><span class="p">[]</span> <span class="n">entries</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Entry</span><span class="p">[</span><span class="n">newSize</span><span class="p">];</span>

<span class="err">  </span><span class="kt">int</span> <span class="n">count</span> <span class="p">=</span> <span class="n">_count</span><span class="p">;</span>
<span class="err">  </span><span class="n">Array</span><span class="p">.</span><span class="nf">Copy</span><span class="p">(</span><span class="n">_entries</span><span class="p">,</span> <span class="n">entries</span><span class="p">,</span> <span class="n">count</span><span class="p">);</span>

<span class="err">  </span><span class="k">if</span> <span class="p">(!</span><span class="k">typeof</span><span class="p">(</span><span class="n">TKey</span><span class="p">).</span><span class="n">IsValueType</span> <span class="p">&amp;&amp;</span> <span class="n">forceNewHashCodes</span><span class="p">)</span>
<span class="err">  </span><span class="p">{</span>
<span class="err">    </span><span class="n">Debug</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="n">_comparer</span> <span class="k">is</span> <span class="n">NonRandomizedStringEqualityComparer</span><span class="p">);</span>
<span class="err">    </span><span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;</span> <span class="n">comparer</span> <span class="p">=</span> <span class="n">_comparer</span> <span class="p">=</span> <span class="p">(</span><span class="n">IEqualityComparer</span><span class="p">&lt;</span><span class="n">TKey</span><span class="p">&gt;)((</span><span class="n">NonRandomizedStringEqualityComparer</span><span class="p">)</span><span class="n">_comparer</span><span class="p">).</span><span class="nf">GetRandomizedEqualityComparer</span><span class="p">();</span>

<span class="err">    </span><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="err">    </span><span class="p">{</span>
<span class="err">      </span><span class="k">if</span> <span class="p">(</span><span class="n">entries</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">next</span> <span class="p">&gt;=</span> <span class="p">-</span><span class="m">1</span><span class="p">)</span>
<span class="err">      </span><span class="p">{</span>
<span class="err">        </span><span class="n">entries</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">hashCode</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">comparer</span><span class="p">.</span><span class="nf">GetHashCode</span><span class="p">(</span><span class="n">entries</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">key</span><span class="p">);</span>
<span class="err">      </span><span class="p">}</span>
<span class="err">    </span><span class="p">}</span>
<span class="err">  </span><span class="p">}</span>
<span class="err">  </span><span class="c1">// ..</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="a-positive-note">A positive note</h1>

<p>Hopefully, you won’t use <code class="language-plaintext highlighter-rouge">StrongTypedKey&lt;T&gt;</code> in your code and will create a new wrapper for each specific case, so the compiler will protect you from messing up with unrelated values. Really, <code class="language-plaintext highlighter-rouge">StronglyTypeKey&lt;T&gt;</code> adds nothing to <code class="language-plaintext highlighter-rouge">T</code> except hiding its operators and methods, you still can assign a <code class="language-plaintext highlighter-rouge">StronglyTypeKey&lt;T&gt;</code> instance to a logically unrelated local or a wrong parameter. What you should do is write down a completely new type for each case like, let’s say, a serial number:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">readonly</span> <span class="n">record</span> <span class="k">struct</span> <span class="nc">SerialNumber</span><span class="p">(</span><span class="kt">string</span> <span class="n">Value</span><span class="p">);</span>
</code></pre></div></div>

<p>Now you cannot accidentally use it for anything else than a serial number, but how is it compared to <code class="language-plaintext highlighter-rouge">string</code> in performance? A simple benchmark for rescue:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="n">SerialNumber</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">_serialNumbers</span> <span class="p">=</span> <span class="k">null</span><span class="p">!;</span>
<span class="k">private</span> <span class="n">SerialNumber</span> <span class="n">_serialNumberToLookup</span><span class="p">;</span>

<span class="k">private</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">_strings</span> <span class="p">=</span> <span class="k">null</span><span class="p">!;</span>
<span class="k">private</span> <span class="kt">string</span> <span class="n">_stringToLookup</span> <span class="p">=</span> <span class="k">null</span><span class="p">!;</span>

<span class="p">[</span><span class="n">GlobalSetup</span><span class="p">]</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">Setup</span><span class="p">()</span>
<span class="p">{</span>
<span class="err">  </span><span class="kt">var</span> <span class="n">data</span> <span class="p">=</span> <span class="n">Enumerable</span><span class="p">.</span><span class="nf">Range</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">N</span><span class="p">).</span><span class="nf">Select</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">ToString</span><span class="p">());</span>

<span class="err">  </span><span class="n">_serialNumbers</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="nf">ToDictionary</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">SerialNumber</span><span class="p">(</span><span class="n">x</span><span class="p">));</span>
<span class="err">  </span><span class="n">_serialNumberToLookup</span> <span class="p">=</span> <span class="n">_serialNumbers</span><span class="p">.</span><span class="nf">Last</span><span class="p">().</span><span class="n">Key</span><span class="p">;</span>

<span class="err">  </span><span class="n">_strings</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="nf">ToDictionary</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">);</span>
<span class="err">  </span><span class="n">_stringToLookup</span> <span class="p">=</span> <span class="n">_strings</span><span class="p">.</span><span class="nf">Last</span><span class="p">().</span><span class="n">Key</span><span class="p">;</span>
<span class="p">}</span>

<span class="p">[</span><span class="nf">Params</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">10</span><span class="p">,</span> <span class="m">100</span><span class="p">,</span> <span class="m">1</span><span class="n">_000</span><span class="p">,</span> <span class="m">10</span><span class="n">_000</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">N</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

<span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">LookupSerialNumber</span><span class="p">()</span> <span class="p">=&gt;</span>
<span class="err">  </span><span class="n">_serialNumbers</span><span class="p">[</span><span class="n">_serialNumberToLookup</span><span class="p">];</span>

<span class="p">[</span><span class="n">Benchmark</span><span class="p">]</span>
<span class="k">public</span> <span class="kt">string</span> <span class="nf">LookupString</span><span class="p">()</span> <span class="p">=&gt;</span>
<span class="err">  </span><span class="n">_strings</span><span class="p">[</span><span class="n">_stringToLookup</span><span class="p">];</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Method             </th>
      <th>N     </th>
      <th style="text-align: right">Mean      </th>
      <th style="text-align: right">Error     </th>
      <th style="text-align: right">StdDev    </th>
      <th style="text-align: right">Code Size</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>LookupSerialNumber</td>
      <td>1     </td>
      <td style="text-align: right">  7.001 ns</td>
      <td style="text-align: right">0.0127 ns</td>
      <td style="text-align: right">0.0119 ns</td>
      <td style="text-align: right">     576 B</td>
    </tr>
    <tr>
      <td>LookupString   </td>
      <td>1     </td>
      <td style="text-align: right">11.311 ns</td>
      <td style="text-align: right">0.1537 ns</td>
      <td style="text-align: right">0.1437 ns</td>
      <td style="text-align: right">     406 B</td>
    </tr>
    <tr>
      <td>LookupSerialNumber</td>
      <td>10    </td>
      <td style="text-align: right">  6.943 ns</td>
      <td style="text-align: right">0.0154 ns</td>
      <td style="text-align: right">0.0128 ns</td>
      <td style="text-align: right">     576 B</td>
    </tr>
    <tr>
      <td>LookupString   </td>
      <td>10    </td>
      <td style="text-align: right">10.798 ns</td>
      <td style="text-align: right">0.0971 ns</td>
      <td style="text-align: right">0.0811 ns</td>
      <td style="text-align: right">     406 B</td>
    </tr>
    <tr>
      <td>LookupSerialNumber</td>
      <td>100 </td>
      <td style="text-align: right">  7.610 ns</td>
      <td style="text-align: right">0.0100 ns</td>
      <td style="text-align: right">0.0093 ns</td>
      <td style="text-align: right">     576 B</td>
    </tr>
    <tr>
      <td>LookupString       </td>
      <td>100 </td>
      <td style="text-align: right">11.104 ns</td>
      <td style="text-align: right">0.2189 ns</td>
      <td style="text-align: right">0.1941 ns</td>
      <td style="text-align: right">     406 B</td>
    </tr>
    <tr>
      <td>LookupSerialNumber</td>
      <td>1000 </td>
      <td style="text-align: right">  7.791 ns</td>
      <td style="text-align: right">0.0771 ns</td>
      <td style="text-align: right">0.0721 ns</td>
      <td style="text-align: right">     576 B</td>
    </tr>
    <tr>
      <td>LookupString   </td>
      <td>1000 </td>
      <td style="text-align: right">11.834 ns</td>
      <td style="text-align: right">0.0832 ns</td>
      <td style="text-align: right">0.0695 ns</td>
      <td style="text-align: right">     397 B</td>
    </tr>
    <tr>
      <td>LookupSerialNumber</td>
      <td>10000</td>
      <td style="text-align: right">  9.538 ns</td>
      <td style="text-align: right">0.0773 ns</td>
      <td style="text-align: right">0.0686 ns</td>
      <td style="text-align: right">     576 B</td>
    </tr>
    <tr>
      <td>LookupString       </td>
      <td>10000</td>
      <td style="text-align: right">11.463 ns</td>
      <td style="text-align: right">0.0966 ns</td>
      <td style="text-align: right">0.0856 ns</td>
      <td style="text-align: right">     406 B</td>
    </tr>
  </tbody>
</table>

<p>Surprisingly, usage of <code class="language-plaintext highlighter-rouge">SerialNumber</code> as a key is a bit faster than with <code class="language-plaintext highlighter-rouge">string</code>, hardly noticeable, but it goes number one. The credit here goes to the JIT compiler which not only used non-shared generics but also devirtualized the string comparison and hash code computation. Now, my readers, you can breathe out and continue using strongly typed identifiers or values without any hesitation.</p>

<h1 id="outro">Outro</h1>

<p>Hope you enjoyed the reading and learned something new about type safety, generics, optimizations, and not less importantly making representative tests. For further reading on the touched topics here’s a list of recommendations:</p>
<ul>
  <li><a href="https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/jit/GuardedDevirtualization.md">CoreCLR Design: Guarded Devirtualization</a></li>
  <li><a href="https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/shared-generics.md">Book of the Runtime: Shared Generics Design</a></li>
  <li><a href="https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/">Why is string.GetHashCode() different each time I run my program in .NET Core?</a></li>
</ul>]]></content><author><name></name></author><category term="C#" /><category term=".NET" /><category term="types" /><category term="optimizations" /><category term="benchmarks" /><category term="JIT" /><category term="assembly" /><category term="hash codes" /><summary type="html"><![CDATA[Recently I read an interesting article by João Antunes covering how different type of .NET collections handle strongly and weakly typed keys, but it lacks a very important part, an analysis of why some results are almost identical in some cases and not in others. So, let’s dive in and find answers to these questions.]]></summary></entry><entry><title type="html">It’s all about properties</title><link href="https://yohdeadfall.github.io/it-is-all-about-properties" rel="alternate" type="text/html" title="It’s all about properties" /><published>2024-02-16T00:00:00+00:00</published><updated>2024-02-16T00:00:00+00:00</updated><id>https://yohdeadfall.github.io/it-is-all-about-properties</id><content type="html" xml:base="https://yohdeadfall.github.io/it-is-all-about-properties"><![CDATA[<p>A few years ago I was working in the entertainment industry and participated in the creation of a brand-new graphical engine and its editor with a primary focus on cinematics. As one might expect the core was made using C++, but for the editor <a href="https://avaloniaui.net/">Avalonia UI</a> was chosen as an easy-to-use cross-platform UI framework. Sure, it wasn’t without the MVVM approach with the support of <a href="https://www.reactiveui.net/">Reactive UI</a>. All of that worked well, but only in the beginning while the editor wasn’t very complex and wasn’t killing the performance by eating a lot of RAM and consuming too much time on event handling. One might say that the toolset was completely wrong and we should have used Y instead of X, but the story isn’t about that. It’s about the investigation being made and an attempt to solve the performance issue.</p>

<p>One day a colleague announced a drop-in replacement for subscriptions and change notifications provided by Reactive UI. The approach provided two options for subscriptions. The first one was about specifying the interested property name, while another one used a LINQ expression from which the property to be listened to was determined. Internally there was a dictionary-based cache to keep observables for requested properties and reuse them. While it worked well, it had a few drawbacks, but before going deeper into it let’s talk about how ReactiveUI works.</p>

<h1 id="reactive-uis-approach">Reactive UI’s approach</h1>

<p>The core of Reactive UI starts from the <code class="language-plaintext highlighter-rouge">IReactiveObject</code> interface:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">interface</span> <span class="nc">IReactiveObject</span> <span class="p">:</span> <span class="n">INotifyPropertyChanged</span><span class="p">,</span> <span class="n">INotifyPropertyChanging</span><span class="p">,</span> <span class="n">IEnableLogger</span>
<span class="p">{</span>
    <span class="k">void</span> <span class="nf">RaisePropertyChanging</span><span class="p">(</span><span class="n">PropertyChangingEventArgs</span> <span class="n">args</span><span class="p">);</span>

    <span class="k">void</span> <span class="nf">RaisePropertyChanged</span><span class="p">(</span><span class="n">PropertyChangedEventArgs</span> <span class="n">args</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As you can easily spot, it’s all about the <code class="language-plaintext highlighter-rouge">System.ComponentModel</code> notification model, <code class="language-plaintext highlighter-rouge">INotifyPropertyChanged</code> and <code class="language-plaintext highlighter-rouge">INotifyPropertyChanging</code> interfaces which were introduced in .NET Framework 2 and used extensively in WinForms applications for binding data in UI. Later the same model was reused in WPF, an MVVM framework where properties were used for data transfer and events for change notifications.</p>

<p>In the code below we implemented a simple class supporting property change notifications:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Person</span> <span class="p">:</span> <span class="n">INotifyPropertyChanged</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">event</span> <span class="n">PropertyChangedEventHandler</span><span class="p">?</span> <span class="n">PropertyChanged</span><span class="p">;</span>

    <span class="k">private</span> <span class="kt">string</span><span class="p">?</span> <span class="n">_firstName</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="n">FirstName</span>
    <span class="p">{</span>
        <span class="k">get</span> <span class="p">=&gt;</span> <span class="n">_firstName</span><span class="p">;</span>
        <span class="k">set</span> <span class="p">=&gt;</span> <span class="nf">Set</span><span class="p">(</span><span class="k">ref</span> <span class="n">_firstName</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="kt">string</span><span class="p">?</span> <span class="n">_lastName</span><span class="p">;</span>
    <span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="n">LastName</span>
    <span class="p">{</span>
        <span class="k">get</span> <span class="p">=&gt;</span> <span class="n">_lastName</span><span class="p">;</span>
        <span class="k">set</span> <span class="p">=&gt;</span> <span class="nf">Set</span><span class="p">(</span><span class="k">ref</span> <span class="n">_lastName</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="n">Set</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">T</span><span class="p">?</span> <span class="n">field</span><span class="p">,</span> <span class="n">T</span><span class="p">?</span> <span class="k">value</span><span class="p">,</span> <span class="p">[</span><span class="n">CallerMemberName</span><span class="p">]</span> <span class="kt">string</span> <span class="n">propertyName</span> <span class="p">=</span> <span class="s">""</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">EqualityComparer</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;.</span><span class="n">Default</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">field</span><span class="p">,</span> <span class="k">value</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="c1">// No event should be fired due to the value being the same</span>
        <span class="p">}</span>
        <span class="k">else</span>
        <span class="p">{</span>
            <span class="n">field</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>

            <span class="c1">// Call handlers only if they are present</span>
            <span class="n">PropertyChanged</span><span class="p">?.</span><span class="nf">Invoke</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">new</span> <span class="nf">PropertyChangedEventArgs</span><span class="p">(</span><span class="n">propertyName</span><span class="p">));</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>ReactiveUI provides us the same capabilities through the abstract <code class="language-plaintext highlighter-rouge">ReactiveObject</code> to be used as a base class and extension methods where <code class="language-plaintext highlighter-rouge">RaiseAndSetIfChanged</code> is for setting a property, while  <code class="language-plaintext highlighter-rouge">WhenAnyValue</code> and friends shall be used for subscriptions. Looks quite simple and user-friendly, but how is it implemented?</p>

<p>The base class has almost nothing interesting inside it. There are just a few fields with lazy initializers. You can see all of them in <a href="https://github.com/reactiveui/ReactiveUI/blob/d75f999be903001bbf3988d8172f93b081ae13af/src/ReactiveUI/ReactiveObject/ReactiveObject.cs">the original code</a>, and here’s a snipped of the most interesting part of it:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ublic</span> <span class="k">class</span> <span class="nc">ReactiveObject</span> <span class="p">:</span> <span class="n">IReactiveNotifyPropertyChanged</span><span class="p">&lt;</span><span class="n">IReactiveObject</span><span class="p">&gt;,</span> <span class="n">IHandleObservableErrors</span><span class="p">,</span> <span class="n">IReactiveObject</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">Lazy</span><span class="p">&lt;</span><span class="n">Unit</span><span class="p">&gt;</span> <span class="n">_propertyChangedEventsSubscribed</span><span class="p">;</span>
    
    <span class="k">public</span> <span class="nf">ReactiveObject</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">_propertyChangingEventsSubscribed</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Lazy</span><span class="p">&lt;</span><span class="n">Unit</span><span class="p">&gt;(</span>
            <span class="p">()</span> <span class="p">=&gt;</span>
            <span class="p">{</span>
               <span class="k">this</span><span class="p">.</span><span class="nf">SubscribePropertyChangingEvents</span><span class="p">();</span>
               <span class="k">return</span> <span class="n">Unit</span><span class="p">.</span><span class="n">Default</span><span class="p">;</span>
            <span class="p">},</span>
            <span class="n">LazyThreadSafetyMode</span><span class="p">.</span><span class="n">PublicationOnly</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">event</span> <span class="n">PropertyChangedEventHandler</span><span class="p">?</span> <span class="n">PropertyChanged</span>
    <span class="p">{</span>
        <span class="k">add</span>
        <span class="p">{</span>
            <span class="n">_</span> <span class="p">=</span> <span class="n">_propertyChangedEventsSubscribed</span><span class="p">.</span><span class="n">Value</span><span class="p">;</span>
            <span class="n">PropertyChangedHandler</span> <span class="p">+=</span> <span class="k">value</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">remove</span> <span class="p">=&gt;</span> <span class="n">PropertyChangedHandler</span> <span class="p">-=</span> <span class="k">value</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">event</span> <span class="n">PropertyChangedEventHandler</span><span class="p">?</span> <span class="n">PropertyChangedHandler</span><span class="p">;</span>

    <span class="k">void</span> <span class="n">IReactiveObject</span><span class="p">.</span><span class="nf">RaisePropertyChanged</span><span class="p">(</span><span class="n">PropertyChangedEventArgs</span> <span class="n">args</span><span class="p">)</span> <span class="p">=&gt;</span>
        <span class="n">PropertyChangedHandler</span><span class="p">?.</span><span class="nf">Invoke</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">args</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It delegates the actual implementation of property subscription to the <code class="language-plaintext highlighter-rouge">SubscribePropertyChangingEvents</code> extension method which belongs to the <code class="language-plaintext highlighter-rouge">IReactiveObjectExtensions</code> static class. The idea behind such implementation is to delay allocations till the moment when someone or something subscribes to <code class="language-plaintext highlighter-rouge">PropertyChanged</code>.</p>

<p>Meanwhile, the real implementation is done in <code class="language-plaintext highlighter-rouge">IReactiveObjectExtensions</code> and it allocates a state object for each reactive object keeping them linked together using a <code class="language-plaintext highlighter-rouge">ConditionalWeakTable&lt;TKey, TValue&gt;</code>. That special type allows linking some data to an object, like <code class="language-plaintext highlighter-rouge">Dictionary&lt;TKey, TValue&gt;</code>, but without storing a strong reference to the key and therefore preventing it from being garbage collected. And lazy initializers are here too for the same reason as in <code class="language-plaintext highlighter-rouge">ReactiveObject</code>, to prevent needless and expensive allocations.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">IReactiveObjectExtensions</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">ConditionalWeakTable</span><span class="p">&lt;</span><span class="n">IReactiveObject</span><span class="p">,</span> <span class="n">IExtensionState</span><span class="p">&lt;</span><span class="n">IReactiveObject</span><span class="p">&gt;&gt;</span> <span class="n">state</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>

    <span class="k">private</span> <span class="k">interface</span> <span class="nc">IExtensionState</span><span class="p">&lt;</span><span class="k">out</span> <span class="n">TSender</span><span class="p">&gt;</span>
        <span class="k">where</span> <span class="n">TSender</span> <span class="p">:</span> <span class="n">IReactiveObject</span>
    <span class="p">{</span>
        <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">IReactivePropertyChangedEventArgs</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;&gt;</span> <span class="n">Changed</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>

        <span class="k">void</span> <span class="nf">SubscribePropertyChangedEvents</span><span class="p">();</span>

        <span class="k">void</span> <span class="nf">RaisePropertyChanged</span><span class="p">(</span><span class="kt">string</span> <span class="n">propertyName</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="n">SubscribePropertyChangedEvents</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">TSender</span> <span class="n">reactiveObject</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TSender</span> <span class="p">:</span> <span class="n">IReactiveObject</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">s</span> <span class="p">=</span> <span class="n">state</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">reactiveObject</span><span class="p">,</span> <span class="n">_</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="n">IExtensionState</span><span class="p">&lt;</span><span class="n">IReactiveObject</span><span class="p">&gt;)</span><span class="k">new</span> <span class="n">ExtensionState</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;(</span><span class="n">reactiveObject</span><span class="p">));</span>

        <span class="n">s</span><span class="p">.</span><span class="nf">SubscribePropertyChangedEvents</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">class</span> <span class="nc">ExtensionState</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">IExtensionState</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;</span>
        <span class="k">where</span> <span class="n">TSender</span> <span class="p">:</span> <span class="n">IReactiveObject</span>
    <span class="p">{</span>
        <span class="k">private</span> <span class="k">readonly</span> <span class="n">TSender</span> <span class="n">_sender</span><span class="p">;</span>
        <span class="k">private</span> <span class="k">readonly</span> <span class="n">Lazy</span><span class="p">&lt;(</span><span class="n">ISubject</span><span class="p">&lt;</span><span class="n">IReactivePropertyChangedEventArgs</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;&gt;</span> <span class="n">subject</span><span class="p">,</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">IReactivePropertyChangedEventArgs</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;&gt;</span> <span class="n">observable</span><span class="p">)&gt;</span> <span class="n">_changed</span><span class="p">;</span>
        <span class="k">private</span> <span class="k">readonly</span> <span class="n">Lazy</span><span class="p">&lt;</span><span class="n">ISubject</span><span class="p">&lt;</span><span class="n">ReactivePropertyChangedEventArgs</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;&gt;&gt;</span> <span class="n">_propertyChanged</span><span class="p">;</span>

        <span class="k">public</span> <span class="nf">ExtensionState</span><span class="p">(</span><span class="n">TSender</span> <span class="n">sender</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">_sender</span> <span class="p">=</span> <span class="n">sender</span><span class="p">;</span>
            <span class="n">_changed</span> <span class="p">=</span> <span class="nf">CreateLazyDelayableSubjectAndObservable</span><span class="p">();</span>
            <span class="n">_propertyChanged</span> <span class="p">=</span> <span class="n">CreateLazyDelayableEventSubject</span><span class="p">&lt;</span><span class="n">ReactivePropertyChangedEventArgs</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;&gt;(</span><span class="n">_sender</span><span class="p">.</span><span class="n">RaisePropertyChanged</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">public</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">IReactivePropertyChangedEventArgs</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;&gt;</span> <span class="n">Changed</span> <span class="p">=&gt;</span> <span class="n">_changed</span><span class="p">.</span><span class="n">Value</span><span class="p">.</span><span class="n">observable</span><span class="p">;</span>

        <span class="k">public</span> <span class="k">void</span> <span class="nf">SubscribePropertyChangedEvents</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_</span> <span class="p">=</span> <span class="n">_propertyChanged</span><span class="p">.</span><span class="n">Value</span><span class="p">;</span>

        <span class="k">public</span> <span class="k">void</span> <span class="nf">RaisePropertyChanged</span><span class="p">(</span><span class="kt">string</span> <span class="n">propertyName</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(!</span><span class="nf">AreChangeNotificationsEnabled</span><span class="p">())</span>
            <span class="p">{</span>
                <span class="k">return</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="kt">var</span> <span class="n">changed</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ReactivePropertyChangedEventArgs</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;(</span><span class="n">_sender</span><span class="p">,</span> <span class="n">propertyName</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">_propertyChanged</span><span class="p">.</span><span class="n">IsValueCreated</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="c1">// Do not use NotifyObservable because event exceptions shouldn't be put in ThrownExceptions</span>
                <span class="n">_propertyChanged</span><span class="p">.</span><span class="n">Value</span><span class="p">.</span><span class="nf">OnNext</span><span class="p">(</span><span class="n">changed</span><span class="p">);</span>
            <span class="p">}</span>

            <span class="k">if</span> <span class="p">(</span><span class="n">_changed</span><span class="p">.</span><span class="n">IsValueCreated</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="nf">NotifyObservable</span><span class="p">(</span><span class="n">_sender</span><span class="p">,</span> <span class="n">changed</span><span class="p">,</span> <span class="n">_changed</span><span class="p">.</span><span class="n">Value</span><span class="p">.</span><span class="n">subject</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">private</span> <span class="k">void</span> <span class="n">NotifyObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">TSender</span> <span class="n">rxObj</span><span class="p">,</span> <span class="n">T</span> <span class="n">item</span><span class="p">,</span> <span class="n">ISubject</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;?</span> <span class="n">subject</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">try</span>
            <span class="p">{</span>
                <span class="n">subject</span><span class="p">?.</span><span class="nf">OnNext</span><span class="p">(</span><span class="n">item</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">rxObj</span><span class="p">.</span><span class="nf">Log</span><span class="p">().</span><span class="nf">Error</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span> <span class="s">"ReactiveObject Subscriber threw exception"</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(!</span><span class="n">_thrownExceptions</span><span class="p">.</span><span class="n">IsValueCreated</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="k">throw</span><span class="p">;</span>
                <span class="p">}</span>

                <span class="n">_thrownExceptions</span><span class="p">.</span><span class="n">Value</span><span class="p">.</span><span class="nf">OnNext</span><span class="p">(</span><span class="n">ex</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>There, in <code class="language-plaintext highlighter-rouge">IReactiveObjectExtensions</code>, exists another method, `RaiseAndSetIfChanged. It is used each time when one needs to assign a new value to some property and raise an event if it’s changed.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">IReactiveObjectExtensions</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">TRet</span> <span class="n">RaiseAndSetIfChanged</span><span class="p">&lt;</span><span class="n">TObj</span><span class="p">,</span> <span class="n">TRet</span><span class="p">&gt;(</span>
        <span class="k">this</span> <span class="n">TObj</span> <span class="n">reactiveObject</span><span class="p">,</span>
        <span class="k">ref</span> <span class="n">TRet</span> <span class="n">backingField</span><span class="p">,</span>
        <span class="n">TRet</span> <span class="n">newValue</span><span class="p">,</span>
        <span class="p">[</span><span class="n">CallerMemberName</span><span class="p">]</span> <span class="kt">string</span><span class="p">?</span> <span class="n">propertyName</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TObj</span> <span class="p">:</span> <span class="n">IReactiveObject</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">propertyName</span> <span class="k">is</span> <span class="k">null</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">propertyName</span><span class="p">));</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">EqualityComparer</span><span class="p">&lt;</span><span class="n">TRet</span><span class="p">&gt;.</span><span class="n">Default</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">backingField</span><span class="p">,</span> <span class="n">newValue</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="n">newValue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">reactiveObject</span><span class="p">.</span><span class="nf">RaisingPropertyChanging</span><span class="p">(</span><span class="n">propertyName</span><span class="p">);</span>
        <span class="n">backingField</span> <span class="p">=</span> <span class="n">newValue</span><span class="p">;</span>
        <span class="n">reactiveObject</span><span class="p">.</span><span class="nf">RaisingPropertyChanged</span><span class="p">(</span><span class="n">propertyName</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">newValue</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">internal</span> <span class="k">static</span> <span class="k">void</span> <span class="n">RaisingPropertyChanged</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">TSender</span> <span class="n">reactiveObject</span><span class="p">,</span> <span class="kt">string</span> <span class="n">propertyName</span><span class="p">)</span>
        <span class="k">where</span> <span class="n">TSender</span> <span class="p">:</span> <span class="n">IReactiveObject</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">propertyName</span> <span class="k">is</span> <span class="k">null</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">propertyName</span><span class="p">));</span>
        <span class="p">}</span>

        <span class="kt">var</span> <span class="n">s</span> <span class="p">=</span> <span class="n">state</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">reactiveObject</span><span class="p">,</span> <span class="n">_</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="n">IExtensionState</span><span class="p">&lt;</span><span class="n">IReactiveObject</span><span class="p">&gt;)</span><span class="k">new</span> <span class="n">ExtensionState</span><span class="p">&lt;</span><span class="n">TSender</span><span class="p">&gt;(</span><span class="n">reactiveObject</span><span class="p">));</span>

        <span class="n">s</span><span class="p">.</span><span class="nf">RaisePropertyChanged</span><span class="p">(</span><span class="n">propertyName</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The method looks exactly as our naive implemntation of <code class="language-plaintext highlighter-rouge">INotifyPropertyChanged</code> except that it has no access to the backing storage of the <code class="language-plaintext highlighter-rouge">PropertyChanged</code> event, and therefore, the extension method invokes <code class="language-plaintext highlighter-rouge">RaisingPropertyChanged</code> on the passed reactive object. <code class="language-plaintext highlighter-rouge">RaisingPropertyChanged</code> in turn fetches the corresponding state from the shared global table and notifies all the subscribers of the object.</p>

<p>That’s non-obvious, but there’s a strong reason why it’s so. It reduces the amount of memory occupied by features that won’t be used. It still takes some space because <code class="language-plaintext highlighter-rouge">Lazy&lt;T&gt;</code> instances and delegates have to be allocated anyway, but that takes less than the whole feature state. Then one can subscribe to an event or a property observable to listen for changes that are going to happen or already happened, four features in total.</p>

<h1 id="avalonia-uis-approach">Avalonia UI’s approach</h1>

<p>Avalonia UI. It started as an attempt to make a cross-platform implementation of WPF with similar APIs but grew into something more powerful with its XPF SDK allowing running WPF applications anywhere Avalonia can go.</p>

<p>The first difference lies in the base classes, <code class="language-plaintext highlighter-rouge">AvaloniaObject</code> and <code class="language-plaintext highlighter-rouge">AvaloniaProperty</code>. While both of them resemble <code class="language-plaintext highlighter-rouge">DependencyObject</code> and <code class="language-plaintext highlighter-rouge">DependencyProperty</code> Avalonia implements it slightly differently by allowing property values to be stored directly in fields in addition to property bags used by styled and attached properties. That’s done for performance-sensitive properties using the <code class="language-plaintext highlighter-rouge">DirectProperty&lt;T&gt;</code> type and not for styling at all. As an example, <code class="language-plaintext highlighter-rouge">Bounds</code> in the <code class="language-plaintext highlighter-rouge">Visual</code> type is a direct property:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">Visual</span> <span class="p">:</span> <span class="n">StyledElement</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">DirectProperty</span><span class="p">&lt;</span><span class="n">Visual</span><span class="p">,</span> <span class="n">Rect</span><span class="p">&gt;</span> <span class="n">BoundsProperty</span> <span class="p">=</span>
        <span class="n">AvaloniaProperty</span><span class="p">.</span><span class="n">RegisterDirect</span><span class="p">&lt;</span><span class="n">Visual</span><span class="p">,</span> <span class="n">Rect</span><span class="p">&gt;(</span><span class="k">nameof</span><span class="p">(</span><span class="n">Bounds</span><span class="p">),</span> <span class="n">o</span> <span class="p">=&gt;</span> <span class="n">o</span><span class="p">.</span><span class="n">Bounds</span><span class="p">);</span>

    <span class="k">public</span> <span class="n">Rect</span> <span class="n">Bounds</span>
    <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_bounds</span><span class="p">;</span> <span class="p">}</span>
        <span class="k">protected</span> <span class="k">set</span> <span class="p">{</span> <span class="nf">SetAndRaise</span><span class="p">(</span><span class="n">BoundsProperty</span><span class="p">,</span> <span class="k">ref</span> <span class="n">_bounds</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Yes, that’s exactly what you have seen before! The same set and raise pattern is also used in Avalonia for direct properties. Moreover, even notification changes happen through the <code class="language-plaintext highlighter-rouge">PropertyChanged</code> event if someone subscribes to it. Otherwise, a better notification change source should be used instead via subscribing to the <code class="language-plaintext highlighter-rouge">Change</code> observer of the interesting property. Note, that this observer should be used for event listening for all living objects. In cases when a specific instance is required, the <code class="language-plaintext highlighter-rouge">GetPropertyChangedObservable</code> extension method should be used instead. A change notification contains such information as the sender, original and new values, priority, and the kind of change, effective or not. When only values are interesting <code class="language-plaintext highlighter-rouge">GetObservable</code> is here with its overloads.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">AvaloniaObject</span> <span class="p">:</span> <span class="n">IAvaloniaObjectDebug</span><span class="p">,</span> <span class="n">INotifyPropertyChanged</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">PropertyChangedEventHandler</span><span class="p">?</span> <span class="n">_inpcChanged</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">EventHandler</span><span class="p">&lt;</span><span class="n">AvaloniaPropertyChangedEventArgs</span><span class="p">&gt;?</span> <span class="n">_propertyChanged</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">event</span> <span class="n">EventHandler</span><span class="p">&lt;</span><span class="n">AvaloniaPropertyChangedEventArgs</span><span class="p">&gt;?</span> <span class="n">PropertyChanged</span>
    <span class="p">{</span>
        <span class="k">add</span> <span class="p">{</span> <span class="n">_propertyChanged</span> <span class="p">+=</span> <span class="k">value</span><span class="p">;</span> <span class="p">}</span>
        <span class="k">remove</span> <span class="p">{</span> <span class="n">_propertyChanged</span> <span class="p">-=</span> <span class="k">value</span><span class="p">;</span> <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">event</span> <span class="n">PropertyChangedEventHandler</span><span class="p">?</span> <span class="n">INotifyPropertyChanged</span><span class="p">.</span><span class="n">PropertyChanged</span>
    <span class="p">{</span>
        <span class="k">add</span> <span class="p">{</span> <span class="n">_inpcChanged</span> <span class="p">+=</span> <span class="k">value</span><span class="p">;</span> <span class="p">}</span>
        <span class="k">remove</span> <span class="p">{</span> <span class="n">_inpcChanged</span> <span class="p">-=</span> <span class="k">value</span><span class="p">;</span> <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">internal</span> <span class="k">void</span> <span class="n">RaisePropertyChanged</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span>
        <span class="n">AvaloniaProperty</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">property</span><span class="p">,</span>
        <span class="n">Optional</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">oldValue</span><span class="p">,</span>
        <span class="n">BindingValue</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">newValue</span><span class="p">,</span>
        <span class="n">BindingPriority</span> <span class="n">priority</span><span class="p">,</span>
        <span class="kt">bool</span> <span class="n">isEffectiveValue</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">e</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AvaloniaPropertyChangedEventArgs</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span>
            <span class="k">this</span><span class="p">,</span>
            <span class="n">property</span><span class="p">,</span>
            <span class="n">oldValue</span><span class="p">,</span>
            <span class="n">newValue</span><span class="p">,</span>
            <span class="n">priority</span><span class="p">,</span>
            <span class="n">isEffectiveValue</span><span class="p">);</span>

        <span class="nf">OnPropertyChangedCore</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">isEffectiveValue</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">property</span><span class="p">.</span><span class="nf">NotifyChanged</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
            <span class="n">_propertyChanged</span><span class="p">?.</span><span class="nf">Invoke</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
            <span class="n">_inpcChanged</span><span class="p">?.</span><span class="nf">Invoke</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">new</span> <span class="nf">PropertyChangedEventArgs</span><span class="p">(</span><span class="n">property</span><span class="p">.</span><span class="n">Name</span><span class="p">));</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">AvaloniaObjectExtensions</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">GetObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">AvaloniaObject</span> <span class="n">o</span><span class="p">,</span> <span class="n">AvaloniaProperty</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">property</span><span class="p">);</span>

    <span class="n">ublic</span> <span class="k">static</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span> <span class="n">GetObservable</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">AvaloniaObject</span> <span class="n">o</span><span class="p">,</span> <span class="n">AvaloniaProperty</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">&gt;</span> <span class="n">property</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">TSource</span><span class="p">,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">converter</span><span class="p">);</span>

    <span class="k">public</span> <span class="k">static</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;</span> <span class="n">GetObservable</span><span class="p">&lt;</span><span class="n">TResult</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">AvaloniaObject</span> <span class="n">o</span><span class="p">,</span> <span class="n">AvaloniaProperty</span> <span class="n">property</span><span class="p">,</span> <span class="n">Func</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">?,</span> <span class="n">TResult</span><span class="p">&gt;</span> <span class="n">converter</span><span class="p">);</span>

    <span class="k">public</span> <span class="k">static</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">AvaloniaPropertyChangedEventArgs</span><span class="p">&gt;</span> <span class="nf">GetPropertyChangedObservable</span><span class="p">(</span>
            <span class="k">this</span> <span class="n">AvaloniaObject</span> <span class="n">o</span><span class="p">,</span>
            <span class="n">AvaloniaProperty</span> <span class="n">property</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">AvaloniaProperty</span><span class="p">&lt;</span><span class="n">TValue</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">AvaloniaProperty</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">new</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">AvaloniaPropertyChangedEventArgs</span><span class="p">&lt;</span><span class="n">TValue</span><span class="p">&gt;&gt;</span> <span class="n">Changed</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Worth mentioning that <code class="language-plaintext highlighter-rouge">AvaloniaObject</code> hides <code class="language-plaintext highlighter-rouge">INotifyPropertyChanged.PropertyChanged</code> and exposes its event providing the same information as a property change observable obtained via <code class="language-plaintext highlighter-rouge">GetPropertyChangedObservable</code> or <code class="language-plaintext highlighter-rouge">AvaloniaProperty&lt;T&gt;.Change</code>. In contradiction to observables, the event allows receiving all property changes of an instance.</p>

<h1 id="one-more-please">One more, please</h1>

<p>Looking back at all the ways we have seen so far there’s a common point. None of them fits well into the .NET model of a property consisting of a getter and a setter method, or at least one of them. There’s no place for events at all! One can write them outside of properties with names consisting of the corresponding property name and the <code class="language-plaintext highlighter-rouge">Changed</code> suffix. If my memory serves me right, WPF can handle that pattern well, but not other frameworks.</p>

<p>Could that be changed? Perhaps, let’s give it a try and write our property using the observable pattern.</p>

<p>Previously we have observed a few different approaches to solve the problem and seen the common point between them. Not it’s time to turn on our imagination and think about how a property should look taking the notification requirement into account.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">struct</span> <span class="nc">Property</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">T</span> <span class="nf">Get</span><span class="p">();</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Set</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">);</span>

    <span class="k">public</span> <span class="n">IObserveable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Changed</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Not yet a complete implementation, but good enough for the start and to evaluate usability.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">person</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Person</span><span class="p">();</span>

<span class="n">person</span><span class="p">.</span><span class="n">FirstName</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">"Alan"</span><span class="p">);</span>
<span class="n">person</span><span class="p">.</span><span class="n">LastName</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">"Turing"</span><span class="p">);</span>

<span class="kt">var</span> <span class="n">first</span> <span class="p">=</span> <span class="n">person</span><span class="p">.</span><span class="n">FirstName</span><span class="p">.</span><span class="nf">Get</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">last</span> <span class="p">=</span> <span class="n">person</span><span class="p">.</span><span class="n">LastName</span><span class="p">.</span><span class="nf">Get</span><span class="p">();</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Property</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">?&gt;</span> <span class="n">FirstName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="n">Property</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">?&gt;</span> <span class="n">LastName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Not so short as .NET properties, but hey, not every language has a concept of properties like, for example, C++ or Rust. Even in .NET properties are made of methods which then invoked when needed. Anything on the top is just syntax sugar. The following snippet demonstrates how <code class="language-plaintext highlighter-rouge">Person</code> with pure .NET properties is compiled into an IL code.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="n">FirstName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="n">LastName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.class public auto ansi beforefieldinit Person
    extends [System.Runtime]System.Object
{
    method public hidebysig specialname 
        instance string get_FirstName () cil managed;

    .method public hidebysig specialname 
        instance void set_FirstName (
            string 'value'
        ) cil managed;

    .method public hidebysig specialname 
        instance string get_LastName () cil managed;

    .method public hidebysig specialname 
        instance void set_LastName (
            string 'value'
        ) cil managed;
}
</code></pre></div></div>

<p>Therefore, without the property concept, it would require us to call <code class="language-plaintext highlighter-rouge">set_FirstName</code> to set a new value and <code class="language-plaintext highlighter-rouge">get_FirstName</code> to get it. Forgetting to use parenthesis for method calls leads to the same error if the <code class="language-plaintext highlighter-rouge">Get</code> method isn’t invoked for a <code class="language-plaintext highlighter-rouge">Property&lt;T&gt;</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Prints "System.Action`1[System.String]"</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="k">new</span> <span class="nf">Person</span><span class="p">().</span><span class="n">SetName</span><span class="p">);</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="kt">string</span><span class="p">?</span> <span class="n">_name</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="nf">GetName</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_name</span><span class="p">;</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">SetName</span><span class="p">(</span><span class="kt">string</span><span class="p">?</span> <span class="k">value</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">_name</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With our proposal, the issue is almost the same, but it leads to a bit different output.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Prints "Property`1[System.String]"</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="k">new</span> <span class="nf">Person</span><span class="p">().</span><span class="n">Name</span><span class="p">);</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Property</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">?&gt;</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>There are not so many options left. Either we abandon the attempt and live in comfort with sugar around .NET properties, or take it as an inevitable cost and move forward with direct method calls. I preferred the last and moved to another not less interesting issue which hasn’t been touched yet. As you might found, value storage was completely omitted in the examples above. Let’s fix it.</p>

<p>Maybe like that?</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">struct</span> <span class="nc">Property</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">T</span> <span class="n">_value</span><span class="p">;</span>

    <span class="k">public</span> <span class="n">T</span> <span class="nf">Get</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_value</span><span class="p">;</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Set</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* set _value and notify observers */</span> <span class="p">}</span>

    <span class="k">public</span> <span class="n">IObserveable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Changed</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Nope, it will never work as expected because each time a property object is accessed its copy will be allocated on the stack. Then it should be close to how direct properties are implemented in Avalonia in terms of keeping the backing fields inside the object directly.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">ref</span> <span class="k">struct</span> <span class="nc">Property</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">ref</span> <span class="n">T</span> <span class="n">_field</span><span class="p">;</span>

    <span class="k">public</span> <span class="n">T</span> <span class="nf">Get</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_field</span><span class="p">;</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Set</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* set _field and notify observers */</span> <span class="p">}</span>

    <span class="k">public</span> <span class="n">IObserveable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Changed</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Closer, but what about “notify observers”? Yeah, an observable object should be stored inside the property owner too. It implies at least one more field, and better not to repeat ourselves and make a base class. Since the number of properties is unknown at compile time and might differ thankfully to type inheritance, property observables will be stored in a list and fetched by relative field addresses.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">ObservableObject</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">PropertyObservable</span><span class="p">&gt;</span> <span class="n">_observables</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>

    <span class="k">internal</span> <span class="n">PropertyObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">GetObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">T</span> <span class="n">field</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">offset</span> <span class="p">=</span> <span class="n">Unsafe</span><span class="p">.</span><span class="nf">ByteOffset</span><span class="p">(</span>
            <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="n">As</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">PropertyObservable</span><span class="p">&gt;,</span> <span class="n">IntPtr</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">_observables</span><span class="p">),</span>
            <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="n">As</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">IntPtr</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">field</span><span class="p">))</span>
    
        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">observable</span> <span class="k">in</span> <span class="n">_observables</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">observable</span><span class="p">.</span><span class="n">Offset</span> <span class="p">==</span> <span class="n">offset</span><span class="p">)</span>
                <span class="k">return</span> <span class="n">observable</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">new</span> <span class="n">PropertyObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">{</span> <span class="n">Offset</span> <span class="p">=</span> <span class="n">offset</span> <span class="p">};</span>

        <span class="n">_observables</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>

        <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">protected</span> <span class="n">Property</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Property</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">T</span> <span class="n">field</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="k">new</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">ref</span> <span class="n">field</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">ref</span> <span class="k">struct</span> <span class="nc">Property</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">ObservableObject</span> <span class="n">_owner</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">ref</span> <span class="n">T</span> <span class="n">_field</span><span class="p">;</span>

    <span class="k">internal</span> <span class="nf">Property</span><span class="p">(</span><span class="n">ObservableObject</span> <span class="n">owner</span><span class="p">,</span> <span class="k">ref</span> <span class="n">T</span> <span class="n">field</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_owner</span> <span class="p">=</span> <span class="n">owner</span><span class="p">;</span>
        <span class="n">_field</span> <span class="p">=</span> <span class="k">ref</span> <span class="n">field</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">T</span> <span class="nf">Get</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_field</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">Set</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">EqualityComparer</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;.</span><span class="n">Default</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">_field</span><span class="p">,</span> <span class="k">value</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="n">_field</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">_field</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
        <span class="n">_owner</span><span class="p">.</span><span class="nf">GetObservable</span><span class="p">(</span><span class="k">ref</span> <span class="n">_field</span><span class="p">).</span><span class="nf">Changed</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">IObserveable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Changed</span> <span class="p">=&gt;</span> <span class="n">_owner</span><span class="p">.</span><span class="nf">GetObservable</span><span class="p">(</span><span class="k">ref</span> <span class="n">_field</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">internal</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">PropertyObservable</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">IntPtr</span> <span class="n">Offset</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="n">required</span> <span class="n">init</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>

<span class="k">internal</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">PropertyObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">PropertyObservable</span><span class="p">,</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Changed</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">);</span>

    <span class="k">public</span> <span class="n">IDisposable</span> <span class="nf">Subscribe</span><span class="p">(</span><span class="n">IObserver</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">observer</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now the <code class="language-plaintext highlighter-rouge">Person</code> type should be inherited from <code class="language-plaintext highlighter-rouge">ObservableObject</code> and instantiate a property when <code class="language-plaintext highlighter-rouge">Name</code> is accessed:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span> <span class="p">:</span> <span class="n">ObservableObject</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="kt">string</span><span class="p">?</span> <span class="n">_name</span><span class="p">;</span>

    <span class="k">public</span> <span class="kt">string</span><span class="p">?</span> <span class="n">Name</span> <span class="p">=&gt;</span> <span class="nf">Property</span><span class="p">(</span><span class="k">ref</span> <span class="n">_name</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With usage like in the following snippet:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">person</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Person</span><span class="p">();</span>

<span class="n">person</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">"Duffy"</span><span class="p">);</span>

<span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="n">person</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="nf">Get</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">changes</span> <span class="p">=</span> <span class="n">person</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="n">Changed</span><span class="p">.</span><span class="nf">Subscribe</span><span class="p">(</span><span class="n">v</span> <span class="p">=&gt;</span> <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">v</span><span class="p">));</span>
</code></pre></div></div>

<p>Have we forgotten anything? Yes, there are two things still left. First, <code class="language-plaintext highlighter-rouge">ref</code> types cannot be used at the moment in generics which quite limits use cases of the <code class="language-plaintext highlighter-rouge">Property&lt;T&gt;</code> type. It can be solved either by making a special delegate type like <code class="language-plaintext highlighter-rouge">SpanAction&lt;T, TArg&gt;</code> for <code class="language-plaintext highlighter-rouge">Span&lt;T&gt;</code>, or by switching to the field offset which is less safe, but more flexible in my opinion.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">readonly</span> <span class="k">struct</span> <span class="nc">Property</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">ObservableObject</span> <span class="n">_owner</span><span class="p">;</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">IntPtr</span> <span class="n">_offset</span><span class="p">;</span>

    <span class="k">internal</span> <span class="nf">Property</span><span class="p">(</span><span class="n">ObservableObject</span> <span class="n">owner</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">offset</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">_owner</span> <span class="p">=</span> <span class="n">owner</span><span class="p">;</span>
        <span class="n">_offset</span> <span class="p">=</span> <span class="n">offset</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="n">T</span> <span class="nf">Get</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_ownerOwner</span><span class="p">.</span><span class="n">Get</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">_offset</span><span class="p">);</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">Set</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">_owner</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="n">_offset</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span>

    <span class="k">public</span> <span class="n">IObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">Changed</span> <span class="p">=&gt;</span> <span class="n">_owner</span><span class="p">.</span><span class="n">GetObservable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">_offset</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As you might know, to get a field offset in .NET one should use reflection since fields might be reordered by runtime if the type isn’t marked as sequential. Reflection isn’t cheap and can be stripped out during AOT compilation, so not an option. Maybe there’s another trick? Sure, a bit of <code class="language-plaintext highlighter-rouge">Unsafe</code> from <code class="language-plaintext highlighter-rouge">System.Runtime.CompilerServices</code>. That type has a pair of methods for address manipulations, <code class="language-plaintext highlighter-rouge">ByteOffset&lt;T&gt;</code> and <code class="language-plaintext highlighter-rouge">AddByteOffset</code>. Surely, we need one more to convert an <code class="language-plaintext highlighter-rouge">IntPtr</code> back to a reference, that’s <code class="language-plaintext highlighter-rouge">As&lt;TFrom, TTo&gt;</code>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">protected</span> <span class="n">IntPtr</span> <span class="n">GetOffsetOf</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">T</span> <span class="n">field</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">Unsafe</span><span class="p">.</span><span class="nf">ByteOffset</span><span class="p">(</span>
        <span class="k">ref</span> <span class="cm">/* wait, we cannot get an instance address easily */</span><span class="p">,</span>
        <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="n">As</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">IntPtr</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">field</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Oh, yeah, there should be the current instance address to get a filed offset, but really, should it be the same as reflection reports? Maybe it can be relative to another field in the base type like it already happens for <code class="language-plaintext highlighter-rouge">GetObservable&lt;T&gt;</code>? Yeah, why not?</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">protected</span> <span class="n">IntPtr</span> <span class="n">GetOffsetOf</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">T</span> <span class="n">field</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">Unsafe</span><span class="p">.</span><span class="nf">ByteOffset</span><span class="p">(</span>
        <span class="k">ref</span> <span class="nf">GetReference</span><span class="p">(),</span>
        <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="n">As</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">IntPtr</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">field</span><span class="p">));</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">ref</span> <span class="n">IntPtr</span> <span class="nf">GetReference</span><span class="p">()</span> <span class="p">=&gt;</span>
    <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="n">As</span><span class="p">&lt;</span><span class="n">PropertyObservable</span><span class="p">?,</span> <span class="n">IntPtr</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">_observables</span><span class="p">);</span>

<span class="k">private</span> <span class="k">ref</span> <span class="n">T</span> <span class="n">GetReference</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">IntPtr</span> <span class="n">offset</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">ref</span> <span class="kt">var</span> <span class="n">baseRef</span> <span class="p">=</span> <span class="k">ref</span> <span class="nf">GetReference</span><span class="p">();</span>
    <span class="k">ref</span> <span class="kt">var</span> <span class="n">valueRef</span> <span class="p">=</span> <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="nf">AddByteOffset</span><span class="p">(</span><span class="k">ref</span> <span class="n">baseRef</span><span class="p">,</span> <span class="n">offset</span><span class="p">);</span>
    <span class="k">return</span> <span class="k">ref</span> <span class="n">Unsafe</span><span class="p">.</span><span class="n">As</span><span class="p">&lt;</span><span class="n">IntPtr</span><span class="p">,</span> <span class="n">T</span><span class="p">&gt;(</span><span class="k">ref</span> <span class="n">valueRef</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">internal</span> <span class="n">T</span> <span class="n">Get</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">IntPtr</span> <span class="n">offset</span><span class="p">)</span> <span class="p">=&gt;</span>
    <span class="n">GetReference</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">offset</span><span class="p">);</span>

<span class="k">internal</span> <span class="k">void</span> <span class="n">Set</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">IntPtr</span> <span class="n">offset</span><span class="p">,</span> <span class="n">T</span> <span class="k">value</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">EqualityComparer</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;.</span><span class="n">Default</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="k">value</span><span class="p">,</span> <span class="n">Get</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">offset</span><span class="p">)))</span>
    <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">GetReference</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">offset</span><span class="p">)</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
    <span class="nf">GetObservable</span><span class="p">(</span><span class="k">ref</span> <span class="n">_field</span><span class="p">).</span><span class="nf">Changed</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Almost ready! Now it’s time for the last thing which is benchmarking to measure how efficient is the alternative implementation for properties. I will not write the full code here. It always can be found in [Kinetic’s repository]*(https://github.com/YohDeadfall/Kinetic/blob/ea0c970dc3d3e184c7ec6d733f4b378a74f20ca7/benches/Kinetic.Benchmarks/Benchmarks.cs#L21-L111) with the tuned version of <code class="language-plaintext highlighter-rouge">ObservableObject</code> and <code class="language-plaintext highlighter-rouge">Property&lt;T&gt;</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>BenchmarkDotNet v0.13.11, Arch Linux
AMD Ryzen 7 4700U with Radeon Graphics, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.100
  [Host]     : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2
  DefaultJob : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2
</code></pre></div></div>

<p>Getters go first as being the most interesting part. If the implementation is noticeably slower than reading a field, it shall not be used in any real application.</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Median</th>
      <th style="text-align: right">Code Size</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>NpcGetter</td>
      <td style="text-align: right">0.0203 ns</td>
      <td style="text-align: right">0.0037 ns</td>
      <td style="text-align: right">0.0035 ns</td>
      <td style="text-align: right">0.0205 ns</td>
      <td style="text-align: right">8 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>KineticGetter</td>
      <td style="text-align: right">0.0076 ns</td>
      <td style="text-align: right">0.0004 ns</td>
      <td style="text-align: right">0.0003 ns</td>
      <td style="text-align: right">0.0077 ns</td>
      <td style="text-align: right">10 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>ReactiveGetter</td>
      <td style="text-align: right">0.0157 ns</td>
      <td style="text-align: right">0.0081 ns</td>
      <td style="text-align: right">0.0075 ns</td>
      <td style="text-align: right">0.0192 ns</td>
      <td style="text-align: right">8 B</td>
      <td style="text-align: right">-</td>
    </tr>
  </tbody>
</table>

<p>Whoa! Not even measurable and just one instruction longer than getting a field via the most simple getter.</p>

<p>Setters time!</p>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>WithObserver</th>
      <th>WithSameValue</th>
      <th style="text-align: right">Mean</th>
      <th style="text-align: right">Error</th>
      <th style="text-align: right">StdDev</th>
      <th style="text-align: right">Gen0</th>
      <th style="text-align: right">Code Size</th>
      <th style="text-align: right">Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>NpcSeter</td>
      <td>False</td>
      <td>False</td>
      <td style="text-align: right">0.8584 ns</td>
      <td style="text-align: right">0.0024 ns</td>
      <td style="text-align: right">0.0022 ns</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">93 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>KineticSetter</td>
      <td>False</td>
      <td>False</td>
      <td style="text-align: right">1.9128 ns</td>
      <td style="text-align: right">0.0105 ns</td>
      <td style="text-align: right">0.0082 ns</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">143 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>ReactiveSetter</td>
      <td>False</td>
      <td>False</td>
      <td style="text-align: right">77.6664 ns</td>
      <td style="text-align: right">1.5666 ns</td>
      <td style="text-align: right">1.4654 ns</td>
      <td style="text-align: right">0.1147</td>
      <td style="text-align: right">668 B</td>
      <td style="text-align: right">240 B</td>
    </tr>
    <tr>
      <td>NpcSeter</td>
      <td>False</td>
      <td>True</td>
      <td style="text-align: right">0.5605 ns</td>
      <td style="text-align: right">0.0014 ns</td>
      <td style="text-align: right">0.0012 ns</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">93 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>KineticSetter</td>
      <td>False</td>
      <td>True</td>
      <td style="text-align: right">0.6122 ns</td>
      <td style="text-align: right">0.0381 ns</td>
      <td style="text-align: right">0.0356 ns</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">107 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>ReactiveSetter</td>
      <td>False</td>
      <td>True</td>
      <td style="text-align: right">2.3611 ns</td>
      <td style="text-align: right">0.0004 ns</td>
      <td style="text-align: right">0.0003 ns</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">113 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>NpcSeter</td>
      <td>True</td>
      <td>False</td>
      <td style="text-align: right">5.2034 ns</td>
      <td style="text-align: right">0.0030 ns</td>
      <td style="text-align: right">0.0026 ns</td>
      <td style="text-align: right">0.0115</td>
      <td style="text-align: right">109 B</td>
      <td style="text-align: right">24 B</td>
    </tr>
    <tr>
      <td>KineticSetter</td>
      <td>True</td>
      <td>False</td>
      <td style="text-align: right">3.2227 ns</td>
      <td style="text-align: right">0.0051 ns</td>
      <td style="text-align: right">0.0048 ns</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">186 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>ReactiveSetter</td>
      <td>True</td>
      <td>False</td>
      <td style="text-align: right">697.0514 ns</td>
      <td style="text-align: right">4.2127 ns</td>
      <td style="text-align: right">3.9406 ns</td>
      <td style="text-align: right">0.3901</td>
      <td style="text-align: right">668 B</td>
      <td style="text-align: right">816 B</td>
    </tr>
    <tr>
      <td>NpcSeter</td>
      <td>True</td>
      <td>True</td>
      <td style="text-align: right">0.5611 ns</td>
      <td style="text-align: right">0.0018 ns</td>
      <td style="text-align: right">0.0017 ns</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">93 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>KineticSetter</td>
      <td>True</td>
      <td>True</td>
      <td style="text-align: right">0.5847 ns</td>
      <td style="text-align: right">0.0097 ns</td>
      <td style="text-align: right">0.0091 ns</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">276 B</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>ReactiveSetter</td>
      <td>True</td>
      <td>True</td>
      <td style="text-align: right">1.1853 ns</td>
      <td style="text-align: right">0.0061 ns</td>
      <td style="text-align: right">0.0057 ns</td>
      <td style="text-align: right">-</td>
      <td style="text-align: right">113 B</td>
      <td style="text-align: right">-</td>
    </tr>
  </tbody>
</table>

<p>Zero allocations are what should be expected because there are no events at all. Just pure propagation of a new value to observers. Still, there’s a surprise even for me, setting a property through <code class="language-plaintext highlighter-rouge">Property&lt;T&gt;</code> isn’t much slower than for a usual .NET property with <code class="language-plaintext highlighter-rouge">INotifyPropertyChanged</code> implemented.</p>

<p>And yet, there’s one open question left. I started the post saying that Avalonia UI is used as a frontend, but how the heck that can be handled in XAML? Well, it’s not the first time I do some magic with custom properties and there’s always a way to do that. It’s even possible to use befriend WPF with it using custom type descriptors from <code class="language-plaintext highlighter-rouge">System.ComponentModel</code> though I haven’t implemented it yet. In Avalonia it differs, the framework has no idea about the component model. Instead, it has data binding extensions that are registered by adding them to the <code class="language-plaintext highlighter-rouge">BindingPlugins.PropertyAccessors</code> list. Even <code class="language-plaintext highlighter-rouge">INotifyPropertyChanged</code> support is done as an extension.</p>

<h1 id="summary">Summary</h1>

<p>As the title states, it’s definitely all about properties. While they make code prettier and easier to write by hiding getter and setter invocations, it doesn’t come without a cost in case of more complex scenarios like data binding and notifications which came with .NET Framework 2.0. Or maybe it’s better to say that the component model doesn’t fit properties well, as it should be done differently if there was a time machine. Who knows?</p>

<p>I feel that it doesn’t matter, but what matters is that there always should be an alternative solution or point of view. It’s even better when none of them are included in the standard library, but very basic blocks to build anything on top of them and not to limit the imagination of developers by some standard implementation written in stone.</p>

<h1 id="acknowledgements">Acknowledgements</h1>

<p>Many thanks to <a href="https://event-driven.io/">Oskar Dudycz</a> and <a href="https://thephd.dev/">JeanHeyd Meneide</a> who did the initial review of this article and provided invaluable suggestions I hope made it less tiresome and easy to read.</p>]]></content><author><name></name></author><category term="C#" /><category term=".NET" /><category term="properties" /><category term="observable" /><category term="reactive" /><category term="UI" /><category term="Avalonia" /><summary type="html"><![CDATA[A few years ago I was working in the entertainment industry and participated in the creation of a brand-new graphical engine and its editor with a primary focus on cinematics. As one might expect the core was made using C++, but for the editor Avalonia UI was chosen as an easy-to-use cross-platform UI framework. Sure, it wasn’t without the MVVM approach with the support of Reactive UI. All of that worked well, but only in the beginning while the editor wasn’t very complex and wasn’t killing the performance by eating a lot of RAM and consuming too much time on event handling. One might say that the toolset was completely wrong and we should have used Y instead of X, but the story isn’t about that. It’s about the investigation being made and an attempt to solve the performance issue.]]></summary></entry></feed>