<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Manohari Jayachandran</title>
    <description>The latest articles on DEV Community by Manohari Jayachandran (@manoharij).</description>
    <link>https://dev.to/manoharij</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3978389%2Fcfe78c8c-e935-4e13-80d4-fc953898678e.png</url>
      <title>DEV Community: Manohari Jayachandran</title>
      <link>https://dev.to/manoharij</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/manoharij"/>
    <language>en</language>
    <item>
      <title>Advanced C# Generics: Contravariance, Custom Comparers and Real Abstractions</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Thu, 02 Jul 2026 21:48:33 +0000</pubDate>
      <link>https://dev.to/manoharij/advanced-c-generics-contravariance-custom-comparersand-real-abstractions-5711</link>
      <guid>https://dev.to/manoharij/advanced-c-generics-contravariance-custom-comparersand-real-abstractions-5711</guid>
      <description>&lt;p&gt;A previous post on &lt;a href="https://www.techstackblog.com/post.html?slug=csharp-generics-collections-explained" rel="noopener noreferrer"&gt;TechStack&lt;/a&gt; blog covered the foundations of C# generics - generic classes, methods, basic constraints, and the five main collection types. A reader on Dev.to asked specifically for a contravariant collection base class example. A separate comment noted the first post was too light for senior level. Both pieces of feedback point at the same gap: there is a meaningful difference between using generics and designing with them. This post covers the patterns that belong in the second category, with complete working code throughout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Recap
&lt;/h2&gt;

&lt;p&gt;The previous post covered these foundations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generic class - type filled at point of use&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;_item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Generic method - type inferred from argument&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;GetFirst&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)!;&lt;/span&gt;

&lt;span class="c1"&gt;// Basic constraints&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;          &lt;span class="c1"&gt;// reference types only&lt;/span&gt;
&lt;span class="nc"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;// has parameterless constructor&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComparable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// supports comparison&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This post builds directly on that foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Covariance and Contravariance, Properly
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Direction Problem
&lt;/h3&gt;

&lt;p&gt;Generics are invariant by default. A &lt;code&gt;List&amp;lt;string&amp;gt;&lt;/code&gt; is not a &lt;code&gt;List&amp;lt;object&amp;gt;&lt;/code&gt;, even though every string is an object. This is correct behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Does NOT compile - correct behavior&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// If it compiled, strings would now contain an int&lt;/span&gt;
&lt;span class="c1"&gt;// and crash at runtime - this is why invariance exists&lt;/span&gt;
&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Covariance and contravariance are the two safe exceptions to this rule, and they work in opposite directions for a specific reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  Covariance - Safe to Widen the Type (out T)
&lt;/h3&gt;

&lt;p&gt;Covariance is safe when a generic type only ever PRODUCES values of T, never accepts them. If you can only read T out, widening is safe because every string you read out is already an object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// IEnumerable&amp;lt;T&amp;gt; is declared as IEnumerable&amp;lt;out T&amp;gt; // It only produces T via foreach, never accepts it IEnumerable&amp;lt;string&amp;gt; strings =&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"world"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Compiles - safe covariance&lt;/span&gt;
&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "hello", "world"&lt;/span&gt;

&lt;span class="c1"&gt;// Your own covariant interface&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IProducer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;Produce&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// only ever RETURNS T - safe to widen&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StringProducer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IProducer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Produce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"from string producer"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;IProducer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringProducer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Compiles because IProducer&amp;lt;out T&amp;gt; is covariant&lt;/span&gt;
&lt;span class="n"&gt;IProducer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Produce&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// "from string producer"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Contravariance - Safe to Narrow the Type (in T)
&lt;/h3&gt;

&lt;p&gt;Contravariance is safe when a generic type only ever CONSUMES values of T, never produces them. If something handles any object, it certainly handles a string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Action&amp;lt;T&amp;gt; is declared as Action&amp;lt;in T&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// It only consumes T as a parameter, never returns it&lt;/span&gt;
&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;processObject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Compiles - safe contravariance&lt;/span&gt;
&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;processString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;processString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// prints "String"&lt;/span&gt;

&lt;span class="c1"&gt;// Your own contravariant interface&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// only ACCEPTS T - safe to narrow&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ObjectPrinter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;IConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;oc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ObjectPrinter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Compiles because IConsumer&amp;lt;in T&amp;gt; is contravariant&lt;/span&gt;
&lt;span class="c1"&gt;// Something that handles object handles string too&lt;/span&gt;
&lt;span class="n"&gt;IConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"works"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// prints "works"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule in plain English: &lt;strong&gt;out T&lt;/strong&gt; means the type only produces T (safe to widen). &lt;strong&gt;in T&lt;/strong&gt; means it only consumes T (safe to narrow).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Contravariant Collection Base Class
&lt;/h3&gt;

&lt;p&gt;Multiple concrete collections sharing a common generic base, usable polymorphically through a contravariant interface - the exact pattern requested on Dev.to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Data models&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="n"&gt;Id&lt;/span&gt;          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Slug&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Tech&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tag&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Author&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Bio&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Abstract generic base - shared behaviour&lt;/span&gt;
&lt;span class="c1"&gt;// without knowing the concrete type&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EntityCollectionBase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;  &lt;span class="n"&gt;Count&lt;/span&gt;          &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetEnumerator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnumerator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;IEnumerator&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnumerator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetEnumerator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Concrete collections - each adds domain-specific methods&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostCollection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityCollectionBase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetByTech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TagCollection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityCollectionBase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Names&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthorCollection&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EntityCollectionBase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;FindByBio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Contravariant consumer interface&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ICollectionProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// A processor for object handles ANY entity collection&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ItemLogger&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICollectionProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"[LOG] &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// The polymorphism in action&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PostCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Advanced Generics"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TagCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Tag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Azure"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;ICollectionProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ItemLogger&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Contravariance - one logger, all entity types&lt;/span&gt;
&lt;span class="n"&gt;ICollectionProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;postLogger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;ICollectionProcessor&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;tagLogger&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;postLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// works&lt;/span&gt;
&lt;span class="n"&gt;tagLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// works&lt;/span&gt;

&lt;span class="c1"&gt;// Generic method that accepts any EntityCollectionBase&amp;lt;T&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;PrintAll&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;EntityCollectionBase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; items"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;PrintAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Post: 1 items&lt;/span&gt;
&lt;span class="nf"&gt;PrintAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Tag: 1 items&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 2: Custom Comparers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  IComparer - Custom Sorting Logic
&lt;/h3&gt;

&lt;p&gt;IComparer lets you define sorting strategy without touching the class being sorted. Writing a generic KeyComparer once lets you sort any type by any key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A generic key comparer - write once, use with any type&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KeyComparer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TKey&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComparer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TKey&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IComparable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TKey&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TKey&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_keySelector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;KeyComparer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TKey&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;keySelector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_keySelector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keySelector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;Compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;_keySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_keySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage - same class, completely different sort keys&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Azure Key Vault"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;13&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"C# Generics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"OAuth Token Flows"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Sort by reading time&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;byReading&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KeyComparer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadingTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byReading&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Result: Azure Key Vault(13), OAuth(15), C# Generics(16)&lt;/span&gt;

&lt;span class="c1"&gt;// Sort by title - same comparer class, different key&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;byTitle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KeyComparer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byTitle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Result: Azure Key Vault, C# Generics, OAuth Token Flows&lt;/span&gt;

&lt;span class="c1"&gt;// You can also use it with LINQ&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sorted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byReading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IEqualityComparer - Custom Equality for Dictionary and HashSet
&lt;/h3&gt;

&lt;p&gt;IEqualityComparer defines what "equal" means when a type is used as a Dictionary key or HashSet member. Both Equals and GetHashCode must stay consistent - equal objects must produce the same hash code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Case-insensitive slug lookup for Dictionary&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaseInsensitiveStringComparer&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IEqualityComparer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetHashCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpperInvariant&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetHashCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Dictionary with case-insensitive keys&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bySlug&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CaseInsensitiveStringComparer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="n"&gt;bySlug&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Azure-Key-Vault"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Azure Key Vault"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// All three find the same post - case doesn't matter&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;p1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bySlug&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"azure-key-vault"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// found&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bySlug&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AZURE-KEY-VAULT"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// found&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;p3&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bySlug&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Azure-Key-Vault"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// found&lt;/span&gt;

&lt;span class="c1"&gt;// Custom Post equality based on slug not reference&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostBySlugComparer&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IEqualityComparer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetHashCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;GetHashCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// HashSet deduplication by slug, not reference&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;uniquePosts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PostBySlugComparer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="n"&gt;uniquePosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Slug&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"azure-key-vault"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Azure Key Vault"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Duplicate - same slug, different Title&lt;/span&gt;
&lt;span class="n"&gt;uniquePosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Slug&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"azure-key-vault"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DUPLICATE ATTEMPT"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniquePosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The key distinction&lt;/strong&gt;: IComparer belongs in sort operations. IEqualityComparer belongs in Dictionary and HashSet construction. Mixing them up compiles but does nothing useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: The notnull Constraint and Generic Factories
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The notnull Constraint
&lt;/h3&gt;

&lt;p&gt;Introduced in C# 8, notnull restricts T to non-nullable types. It makes intent explicit at compile time rather than leaving null as a runtime surprise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Without notnull - null can silently slip through&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;FindFirst&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Unsafe - suppresses nullable warning with !&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;)!;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// With notnull - throw rather than return null&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;FindOrThrow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;notnull&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
               &lt;span class="s"&gt;"No matching item found"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// default(T) is different per type - always know which:&lt;/span&gt;
&lt;span class="c1"&gt;// string      -&amp;gt; null&lt;/span&gt;
&lt;span class="c1"&gt;// int         -&amp;gt; 0&lt;/span&gt;
&lt;span class="c1"&gt;// bool        -&amp;gt; false&lt;/span&gt;
&lt;span class="c1"&gt;// DateTime    -&amp;gt; DateTime.MinValue (0001-01-01)&lt;/span&gt;
&lt;span class="c1"&gt;// custom class -&amp;gt; null&lt;/span&gt;

&lt;span class="c1"&gt;// Safe dictionary lookup with notnull key&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;GetOrThrow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;notnull&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;KeyNotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="s"&gt;$"Key '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' not found"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"azure-key-vault"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Azure Key Vault"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"azure-key-vault"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// found.Title = "Azure Key Vault"&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"doesnt-exist"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// throws KeyNotFoundException: Key 'doesnt-exist' not found&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generic Factory with Validation
&lt;/h3&gt;

&lt;p&gt;A generic factory pairs where T : new() with an Action for configuration and a Func for validation - construction, configuration, and validation as composable arguments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;CreateValidated&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;errorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Validation failed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;T&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;span class="c1"&gt;// Usage - throws if post has no title or slug&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CreateValidated&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Advanced Generics"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"csharp-advanced-generics"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Post must have a title and slug"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// This throws&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invalid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CreateValidated&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Post must have a title"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// throws InvalidOperationException: Post must have a title&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 4: Generic Repository with Multiple Constraints
&lt;/h2&gt;

&lt;p&gt;Combining constraints shows what the constraint system looks like composed into a real, reusable abstraction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Marker interface - only domain entities allowed&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Post now implements IEntity&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IEntity&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="n"&gt;Id&lt;/span&gt;          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Slug&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Tech&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Four constraints combined in one declaration:&lt;/span&gt;
&lt;span class="c1"&gt;// class   - must be a reference type&lt;/span&gt;
&lt;span class="c1"&gt;// IEntity - must have an Id property&lt;/span&gt;
&lt;span class="c1"&gt;// notnull - must not be nullable&lt;/span&gt;
&lt;span class="c1"&gt;// new()   - must be constructable with new T()&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RepositoryBase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IEntity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;notnull&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_store&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; with Id "&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; already exists"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;GetById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsReadOnly&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Create, configure, validate, and add in one call&lt;/span&gt;
    &lt;span class="c1"&gt;// new() constraint lets us call new T() here&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;CreateAndAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;T&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validate&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;$"Validation failed for "&lt;/span&gt;
                &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Concrete repository - inherits everything above,&lt;/span&gt;
&lt;span class="c1"&gt;// adds Post-specific queries&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostRepository&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RepositoryBase&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetByTech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;GetAll&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;tech&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;GetBySlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;GetAll&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Override to add Post-specific validation&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Post must have a slug"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PostRepository&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Create, configure, validate, and add in one call&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAndAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Advanced Generics"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"csharp-advanced-generics"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"C#"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Retrieve&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBySlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"csharp-advanced-generics"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// "Advanced Generics"&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;csharpPosts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetByTech&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"C#"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csharpPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// 1&lt;/span&gt;

&lt;span class="c1"&gt;// Delete&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;deleted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAll&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 5: Generic Extension Methods
&lt;/h2&gt;

&lt;p&gt;Extension methods can be generic, making a single implementation available across every type satisfying the constraint - write once, use everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CollectionExtensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Split any IEnumerable&amp;lt;T&amp;gt; into two lists&lt;/span&gt;
    &lt;span class="c1"&gt;// by a predicate - works for any type&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Matching&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NotMatching&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Partition&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;matching&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;notMatching&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;matching&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="n"&gt;notMatching&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matching&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;notMatching&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Safe Dictionary conversion - throws on duplicates&lt;/span&gt;
    &lt;span class="c1"&gt;// rather than silently overwriting like ToDictionary&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;ToDictionaryOrThrow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TKey&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TKey&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;keySelector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TKey&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;notnull&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;keySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;$"Duplicate key detected: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

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

    &lt;span class="c1"&gt;// Add an item to any ICollection&amp;lt;T&amp;gt; and return it&lt;/span&gt;
    &lt;span class="c1"&gt;// Useful for fluent initialization chains&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;AddAndReturn&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;ICollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage - all three extensions work on any type&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Azure Key Vault"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;13&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"C# Generics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"OAuth Token Flows"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Azure Service Bus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Partition - split by reading time&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;longPosts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shortPosts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Partition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Long: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Long: 2&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Short: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shortPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Short: 2&lt;/span&gt;

&lt;span class="c1"&gt;// ToDictionaryOrThrow - safe Dictionary from any list&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bySlug&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToDictionaryOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// AddAndReturn - fluent chain&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;addedPost&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAndReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"New Post"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addedPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "New Post"&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use &lt;strong&gt;covariance (out T)&lt;/strong&gt; on interfaces that only produce T - widening is safe because every value coming out already satisfies the broader type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;strong&gt;contravariance (in T)&lt;/strong&gt; on interfaces that only consume T - narrowing is safe because a consumer built for a broader type handles any narrower one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;IComparer&lt;/strong&gt; belongs in sort operations - Sort(), OrderBy(). &lt;strong&gt;IEqualityComparer&lt;/strong&gt; belongs in Dictionary and HashSet construction. They look similar and solve completely different problems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;notnull constraint&lt;/strong&gt; makes nullability intent explicit at compile time rather than leaving null as a runtime surprise for callers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;default(T)&lt;/strong&gt; behaves differently for reference and value types - always know which category T falls into in context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generic extension methods&lt;/strong&gt; multiply their usefulness because they work across every type satisfying the constraint, not just one specific type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiple constraints combined - class, IEntity, notnull, new() - express a precise contract a concrete type must satisfy, and the base class can rely on every guarantee throughout its implementation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The patterns in this post belong in real library and framework design rather than just application code. Covariance and contravariance enable polymorphism across generic types without unsafe casting. Custom comparers&lt;br&gt;
separate ordering and equality logic from data classes. The generic repository with multiple constraints shows the constraint system composed into a real, reusable abstraction. Generic extension methods multiply their usefulness across the entire type system.&lt;/p&gt;

&lt;p&gt;These are the generics patterns worth knowing when the goal is designing things other code depends on, not just using things that someone else already built.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=csharp-advanced-generics" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=csharp-advanced-generics&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First generics post (foundations):&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=csharp-generics-collections-explained" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=csharp-generics-collections-explained&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More from TechStack Blog:&lt;br&gt;
C# / .NET: &lt;a href="https://www.techstackblog.com/category.html?cat=csharp" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=csharp&lt;/a&gt;&lt;br&gt;
Azure: &lt;a href="https://www.techstackblog.com/category.html?cat=azure" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=azure&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
      <category>advanced</category>
    </item>
    <item>
      <title>OAuth Token Flows and Microsoft Entra ID: How One Application Proves Who It Is to Another</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Tue, 30 Jun 2026 16:33:15 +0000</pubDate>
      <link>https://dev.to/manoharij/oauth-token-flows-and-microsoft-entra-id-how-oneapplication-proves-who-it-is-to-another-3h46</link>
      <guid>https://dev.to/manoharij/oauth-token-flows-and-microsoft-entra-id-how-oneapplication-proves-who-it-is-to-another-3h46</guid>
      <description>&lt;p&gt;A post on my TechStack blog about Azure Key Vault answered the question of where secrets live. This one answers a different, equally fundamental question: how does one application actually prove its identity to another and stay proven over time without constant re-authentication. Managing exactly this - a three-month token refresh cycle for one external system, a six-month cycle for another - was a real operational responsibility on an Azure-based integration platform at Blue Yonder. This post explains the mechanism underneath that responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Microsoft Entra ID Actually Is
&lt;/h2&gt;

&lt;p&gt;Microsoft Entra ID, formerly Azure Active Directory and still commonly called Azure AD, is Microsoft's identity platform. It is where users, applications, and services prove who they are before being allowed to do anything at all. Every Managed Identity referenced in earlier posts on this blog is, underneath, an Entra ID identity using exactly the mechanism described here.&lt;/p&gt;

&lt;p&gt;Think of a hotel. Checking in verifies identity once - a passport, a reservation, a form of payment. In exchange, the front desk issues a key card. The card itself does not prove who someone is; it proves they were verified recently and are permitted into specific rooms for a specific window of time. The card eventually expires. When it does, full check-in is not required again - showing the old card along with proof of still being a guest is enough to get a new one.&lt;/p&gt;

&lt;p&gt;An access token is the key card. The identity verification at check-in is the OAuth flow itself. A refresh token is the proof shown at the desk to get a new card without redoing the full check-in process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Vocabulary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;Resource Owner&lt;/em&gt; is the user, or in service-to-service contexts the system, that owns the data being accessed. &lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Client&lt;/em&gt; is the application requesting access - a web app, a backend service, a mobile app. &lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Authorization Server&lt;/em&gt; issues tokens after verifying identity, and in this context that role belongs to Entra ID. &lt;/li&gt;
&lt;li&gt;The &lt;em&gt;Resource Server&lt;/em&gt; is the API that actually holds the requested data Salesforce's API, ServiceNow's API, or an internal API.&lt;/li&gt;
&lt;li&gt;An &lt;em&gt;Access Token&lt;/em&gt; is short-lived proof of authorization, the key card itself, sent with every API request typically in the Authorization header. A &lt;em&gt;Refresh Token&lt;/em&gt; is a longer-lived credential used to obtain a new access token without re-authenticating from scratch. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Scope&lt;/em&gt; defines what a token is actually permitted to do - read access to a resource is a meaningfully different scope from read and write access to that same resource.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Client Credentials Flow
&lt;/h2&gt;

&lt;p&gt;This is the flow that matters most for backend integrations - precisely the kind of system-to-system connection that powers an integration platform. No human is involved at any point. One application proves its own identity directly in order to gain access to another application system's API.&lt;/p&gt;

&lt;p&gt;The application holds a &lt;em&gt;Client ID&lt;/em&gt; and either a &lt;em&gt;Client Secret&lt;/em&gt; or a certificate, registered in Entra ID ahead of time. It sends those credentials directly to the Entra ID token endpoint. Entra ID verifies the credentials and returns an access token - no user, no browser redirect, no login screen anywhere in this entire exchange. The application includes that access token in the Authorization header of every subsequent API call to the target system. When the access token expires, commonly after one hour, the application requests a new one using the same client credentials, and the cycle repeats automatically - usually handled transparently by a library rather than custom code.&lt;/p&gt;

&lt;p&gt;In C#, using the Microsoft.Identity.Client (MSAL) NuGet package, this means building a ConfidentialClientApplication with the client ID, client secret, and tenant authority, then calling AcquireTokenForClient with the target scope. MSAL caches the resulting token automatically and silently refreshes it on subsequent calls if the cached token has expired - there is no need to write manual expiry-checking logic in application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorization Code Flow
&lt;/h2&gt;

&lt;p&gt;This is the flow behind every "Sign in with Microsoft" button. Unlike client credentials, a real human user is present and must explicitly approve the requested access. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user clicks sign in, and the browser redirects to Entra ID's login page.&lt;/li&gt;
&lt;li&gt;The user authenticates - password, multi-factor authentication, whatever policy requires - and approves the requested scopes. &lt;/li&gt;
&lt;li&gt;Entra ID redirects back to the application with a short-lived authorization code, deliberately not a token yet. &lt;/li&gt;
&lt;li&gt;The application's backend then exchanges that code, along with its own client secret, for an access token and a refresh token. &lt;/li&gt;
&lt;li&gt;The access token handles subsequent API calls; when it expires, the refresh token obtains a new access token without requiring the user to log in again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reason the code-for-token exchange happens server-side, separately from the browser redirect, is a deliberate security measure. The authorization code passes through the user's browser and could theoretically be intercepted, but it is useless on its own without the client secret, which never leaves the application's backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Access Tokens Are Deliberately Short-Lived
&lt;/h2&gt;

&lt;p&gt;If an access token leaks - logged accidentally, intercepted in transit, stored somewhere insecure - a short expiry limits the actual damage window. A typical access token lifetime is around one hour. A typical refresh token lifetime runs closer to ninety days, often configurable, and frequently with sliding expiration where each use resets the clock.&lt;/p&gt;

&lt;p&gt;This is precisely why token refresh cycles become a genuine operational concern in production systems. A refresh token itself eventually expires too, and if nothing actively refreshes it before that happens, the integration breaks until someone manually re-authenticates from scratch. The three-month and six-month cycles managed at Blue Yonder for two different external systems were exactly this scenario - each external provider enforced its own refresh token lifetime, and centralizing those credentials in Key Vault meant there was one governed place to monitor expiry and rotate credentials proactively, rather than discovering an integration had silently broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a JWT Actually Contains
&lt;/h2&gt;

&lt;p&gt;Most Entra ID access tokens are JSON Web Tokens, a specific and inspectable format rather than a random opaque string. &lt;br&gt;
A JWT consists of three parts separated by dots - &lt;br&gt;
a &lt;em&gt;header&lt;/em&gt; indicating which algorithm signed the token, &lt;br&gt;
a &lt;em&gt;payload&lt;/em&gt; containing the actual claims (who issued it, who it is intended for, what scopes it carries, when it expires), and&lt;br&gt;
a &lt;em&gt;signature&lt;/em&gt; providing cryptographic proof the token has not been tampered with.&lt;/p&gt;

&lt;p&gt;A critical detail: JWTs are signed, not encrypted. The payload is fully readable by anyone holding the token, even without any ability to forge a new one. This is precisely why a JWT should never contain sensitive data directly in its claims - readability is not a bug in this format, it is the design and treating the payload as confidential is a genuine security mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validating a Token on the Receiving Side
&lt;/h2&gt;

&lt;p&gt;Protecting an API endpoint with token validation in ASP.NET Core involves configuring JWT Bearer authentication with the expected authority and audience, and enabling validation of the issuer, audience, lifetime, and signing key. Once configured, an Authorize attribute on a controller action is enough to require a valid token - the middleware itself handles signature verification, expiry checking, issuer checking, and audience checking automatically none of which needs to be written by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh Token Rotation
&lt;/h2&gt;

&lt;p&gt;Conceptually, refreshing an access token means sending a request to the token endpoint with a grant type of refresh token, the client credentials, and the previously stored refresh token. The response contains a new access token, and frequently a brand new refresh token as well - a pattern called refresh token rotation, where the old refresh token is invalidated the moment a new one is issued. &lt;/p&gt;

&lt;p&gt;This rotation behavior is worth knowing about specifically, because it is a genuine, subtle source of production bugs. If a system stores a refresh token and the provider rotates it on every use, the stored copy must be updated after every single refresh. Skip that update once, and the next refresh attempt fails with an invalidated token - often discovered only when an integration that worked fine in testing quietly stops working in production weeks later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Storing access tokens long-term defeats their entire security model - they are designed to be short-lived and re-fetched frequently, not cached for days.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hardcoding a client secret directly in source code repeats the exact mistake covered in the Key Vault post on this blog - client secrets belong in Key Vault or GitHub Secrets, never committed alongside application code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Failing to handle refresh token rotation correctly, as described above, causes integrations to fail unpredictably after their first successful refresh cycle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Requesting overly broad scopes - asking for full read and write access when only read access is actually needed - is a real, avoidable security smell worth catching in code review.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confusing authentication with authorization is a more conceptual mistake: a valid token proves who is asking, but it does not automatically mean they are permitted to do what they are asking. That second check is a separate concern, typically handled through scopes or application roles layered on top of basic token validity.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Choosing the Right Flow
&lt;/h2&gt;

&lt;p&gt;Client Credentials Flow fits service-to-service scenarios with no human user present - backend integrations calling another backend API, scheduled jobs, daemon processes, and the Salesforce and ServiceNow style integrations referenced throughout this blog's other posts. Authorization Code Flow fits scenarios where a real human user needs to log in and the application acts on that specific user's behalf - any "sign in with Microsoft" style scenario.&lt;/p&gt;

&lt;p&gt;Neither flow is more secure than the other in the abstract; they solve genuinely different problems. Using client credentials for a user-facing login, or authorization code for a headless backend job, are both signs of reaching for the wrong flow for the actual situation at hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Connects to the Rest of This Blog's Azure Coverage
&lt;/h2&gt;

&lt;p&gt;Key Vault, covered in an earlier post, is where the client secret in this entire flow actually lives. Managed Identity, covered in that same post, is in fact a special case of this exact OAuth machinery - an Azure resource receives an automatically managed Entra ID identity, and the client credentials flow happens entirely behind the scenes with no secret for a developer to manage at all. Application Insights, covered in an even earlier post, is where a failed token refresh would actually surface in production - an exception appearing in the logs, a spike in 401 responses, exactly the kind of pattern a KQL query against the requests table would catch quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Client Credentials Flow is the right mental model for almost every backend-to-backend Azure integration - two systems proving identity to each other with no user involved anywhere in the exchange.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refresh tokens exist specifically so a long-running integration does not require constant manual re-authentication, but they still expire, and that expiry genuinely needs active, ongoing monitoring. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refresh token rotation is a frequent, subtle source of production bugs whenever a stored token is not updated on every single refresh.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A JWT's payload is readable by anyone holding the token - sensitive data should never be placed directly in token claims.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Managed Identity removes the need to manage a client secret at all by having Azure itself handle the client credentials flow transparently on a resource's behalf. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Requesting the minimum scope an integration genuinely needs, rather than the broadest available scope, is a simple and frequently overlooked security improvement.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;OAuth 2.0&lt;/em&gt; is the mechanism underneath nearly every system-to-system connection in enterprise Azure architecture. &lt;br&gt;
&lt;em&gt;Client Credentials Flow&lt;/em&gt; handles service-to-service trust with no user involved at all. &lt;br&gt;
&lt;em&gt;Authorization Code Flow&lt;/em&gt; handles a real user logging in and delegating access. &lt;br&gt;
&lt;em&gt;Access tokens&lt;/em&gt; are short-lived by design; refresh tokens exist to renew them quietly behind the scenes, and both carry real expiry windows that demand active management in any long-running integration. &lt;/p&gt;

&lt;p&gt;Understanding this properly is what separates simply knowing an API call works from actually understanding why it works - and why it eventually stops working if nobody is watching the token lifecycle underneath it.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=azure-ad-oauth-token-flows-explained" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=azure-ad-oauth-token-flows-explained&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Related reading on TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=azure-key-vault-secrets-management" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=azure-key-vault-secrets-management&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More from TechStack Blog, by category:&lt;br&gt;
Azure: &lt;a href="https://www.techstackblog.com/category.html?cat=azure" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=azure&lt;/a&gt;&lt;br&gt;
C# / .NET: &lt;a href="https://www.techstackblog.com/category.html?cat=csharp" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=csharp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow for weekly posts on Azure, C#, and cloud engineering.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>oauth</category>
      <category>dotnet</category>
      <category>security</category>
    </item>
    <item>
      <title>C# Generics and Collections: The Interview Topic That Comes Up Every Single Time</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Sun, 28 Jun 2026 14:01:57 +0000</pubDate>
      <link>https://dev.to/manoharij/c-generics-and-collections-the-interview-topic-thatcomes-up-every-single-time-240o</link>
      <guid>https://dev.to/manoharij/c-generics-and-collections-the-interview-topic-thatcomes-up-every-single-time-240o</guid>
      <description>&lt;p&gt;Of all the C# topics that come up in technical interviews, generics and collections show up the most consistently - across junior, mid, and senior level conversations. Not because they are exotic, but because they are the foundation almost every other piece of C# code sits on top of. This post covers both properly, with the analogies that made them click and real code from the kind of work that actually shows up in production systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Generics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem Generics Solve
&lt;/h3&gt;

&lt;p&gt;Before generics existed in C#, building a container that could hold any type meant choosing between two bad options: writing a separate class for every single type - endless, near-identical duplication - or using the base object type and casting everything, which threw away compile-time type safety and added a real performance cost for value types through boxing.&lt;/p&gt;

&lt;p&gt;Think of a generic class like a shipping container with a label you fill in later. The container itself - its size, its locking mechanism, how it gets loaded onto a ship - is exactly the same regardless of what goes inside. You write the container once. At the moment you actually use it, you decide: this one holds books, this one holds electronics, this one holds furniture. The container does not need to be rebuilt for each cargo type - but once declared to hold books, it refuses to let someone load a refrigerator into it instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic Classes in Practice
&lt;/h3&gt;

&lt;p&gt;A generic class declares one or more type parameters, conventionally named T, that get filled in at the point of use rather than when the class is written. A simple Box class written once as &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; works identically well for &lt;code&gt;Box&amp;lt;Post&amp;gt;, Box&amp;lt;int&amp;gt;, or Box&amp;lt;string&amp;gt;&lt;/code&gt; - the compiler enforces that whatever type was specified at creation is the only type that class instance will ever accept, with zero runtime casting required anywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic Methods
&lt;/h3&gt;

&lt;p&gt;The same idea applies at the method level without requiring an entire generic class. A method that returns the first item in any list, or null if the list is empty, can be written once as a generic method and called with a list of posts, a list of integers, or a list of strings equally correctly, with the compiler inferring the type parameter automatically from what gets passed in. &lt;/p&gt;

&lt;p&gt;This is, in fact, exactly what every LINQ method already does. Where and Select are generic methods under the hood - the reason they work identically well on a list of blog posts and a list of integers is the same generic mechanism covered here, not separate special-cased implementations for every possible type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic Constraints
&lt;/h3&gt;

&lt;p&gt;Sometimes a generic container should not accept literally any type - it should only accept types with certain specific capabilities. Constraints express that restriction directly in the type parameter declaration.&lt;/p&gt;

&lt;p&gt;A constraint of &lt;code&gt;where T : class&lt;/code&gt; restricts T to reference types only. &lt;br&gt;
A constraint of &lt;code&gt;where T : IComparable&amp;lt;T&amp;gt;&lt;/code&gt; guarantees that comparison operations are actually available inside the generic code, which matters&lt;br&gt;
enormously for something like a generic GetMax method that needs to compare items to find the largest one. &lt;br&gt;
A constraint of &lt;code&gt;where T : new()&lt;/code&gt; guarantees a public parameterless constructor exists, which is required before generic code can legally write new T() to create an instance. &lt;br&gt;
Multiple constraints can combine - a repository base class might reasonably require &lt;code&gt;where T : class, IEntity, new()&lt;/code&gt; all at once.&lt;/p&gt;

&lt;p&gt;A small but realistic generic repository pattern over Entity Framework Core illustrates this well: a single Repository class, constrained to &lt;code&gt;where T : class&lt;/code&gt;, provides GetByIdAsync and GetAllAsync methods that work correctly for Post, Tag, or Comment entities without writing three nearly identical repository classes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Covariance and Contravariance
&lt;/h3&gt;

&lt;p&gt;This is the part interviewers most often use to probe deeper understanding, and it is genuinely simpler than it sounds once translated out of the in/out keyword syntax into plain language.&lt;/p&gt;

&lt;p&gt;Covariance, marked with the out keyword in an interface declaration, allows a more derived type to be used where a less derived type was originally specified. IEnumerable is declared as covariant, which is why a List&amp;lt;string&amp;gt; can be assigned to an IEnumerable&amp;lt;object&amp;gt; variable without any cast - if something only ever produces values of type T by reading them out, it is probably safe to treat every string as the&lt;br&gt;
object it also happens to be.&lt;/p&gt;

&lt;p&gt;Contravariance, marked with the in keyword, runs the opposite direction - it allows a less derived type to be used where a more derived type was specified. Action is contravariant, which is why an Action&amp;lt;object&amp;gt; can be assigned to an Action&amp;lt;string&amp;gt; variable - something that knows how to process any object can certainly handle the more specific case of a string, since a string satisfies every requirement an object-handling method might have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Collections
&lt;/h2&gt;

&lt;p&gt;If generics are the shipping container with a fillable label, collections are the different container designs built on top of that same underlying pattern - a numbered shelf, a labeled drawer system where each drawer has exactly one name, a single-entry tube, a stack of plates where only the top one is reachable. Same generic foundation, shaped differently for different access patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  List&amp;lt;T&amp;gt;
&lt;/h3&gt;

&lt;p&gt;The default, ordered, resizable collection - genuinely the correct first choice in the large majority of real-world cases. List preserves insertion order, supports fast indexed access, and handles frequent additions and removals well. Use it whenever order matters and there is no specific reason to reach for something more specialized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dictionary&amp;lt;TKey, TValue&amp;gt;
&lt;/h3&gt;

&lt;p&gt;The collection built specifically for fast lookup by a unique key. Where List.Contains() or a manual loop scans through every item until it finds a match - an O(n) operation that gets slower as the collection grows - Dictionary lookup by key is close to O(1) regardless of how many entries exist. The moment "look this thing up by some identifier" becomes a frequent operation in a piece of code, that is the signal to reach for a Dictionary rather than continuing to scan a List.&lt;/p&gt;

&lt;p&gt;TryGetValue provides a safe lookup pattern that avoids exceptions when a key might not exist, which is almost always preferable to a direct indexer access wrapped in a try/catch.&lt;/p&gt;

&lt;h3&gt;
  
  
  HashSet&amp;lt;T&amp;gt;
&lt;/h3&gt;

&lt;p&gt;A collection whose entire purpose is guaranteeing uniqueness and supporting fast set operations. Adding a duplicate value to a HashSet simply does nothing rather than creating a second copy and checking whether a value exists runs in close to O(1) time rather than the O(n) scan a List would require. Beyond simple uniqueness, HashSet supports genuine set operations - union, intersection, and except - that are easy to forget exist but are often exactly the right tool when comparing two collections of tags, categories, or identifiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queue&amp;lt;T&amp;gt; and Stack&amp;lt;T&amp;gt;
&lt;/h3&gt;

&lt;p&gt;Two collections where the access pattern itself is the entire point.&lt;br&gt;
Queue is first-in-first-out, conceptually identical to a line at a coffee shop - the first item added is the first item retrieved. &lt;br&gt;
Stack is last-in-first-out, conceptually identical to a stack of plates - the most recently added item is the first one retrieved. These are not purely academic constructs invented for textbooks - every running program's call stack is, quite literally, a stack of execution frames, and many real messaging systems implement queue-like processing semantics for exactly the reasons a Queue would suggest.&lt;/p&gt;

&lt;h3&gt;
  
  
  IEnumerable&amp;lt;T&amp;gt; vs IList&amp;lt;T&amp;gt; vs List&amp;lt;T&amp;gt;
&lt;/h3&gt;

&lt;p&gt;This is close to a guaranteed interview question in some form, and the honest answer is a hierarchy of guarantees rather than a single right answer.&lt;/p&gt;

&lt;p&gt;IEnumerable is the most general - it guarantees only that the collection can be iterated once with foreach, with no promise of indexed access or even an efficient Count. LINQ queries are returned as IEnumerable specifically because of deferred execution, where the underlying work has not actually run yet at the point the type is returned.&lt;/p&gt;

&lt;p&gt;IList adds indexed access, a real Count property, and Insert/RemoveAt methods, while still remaining an interface rather than a concrete implementation - useful when a method needs indexing but should stay abstract about exactly which concrete collection type a caller provides.&lt;/p&gt;

&lt;p&gt;List is the concrete implementation most code actually instantiates and works with directly.&lt;/p&gt;

&lt;p&gt;The practical, interview-relevant rule that ties this together: accept the most general type a method actually needs as a parameter - frequently IEnumerable, since many methods only need to iterate once - and return the most specific type that gives callers genuinely useful guarantees, frequently List or an array, rather than hiding a concrete result behind an unnecessarily abstract interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the Old Non-Generic Collections Should Never Appear in New Code
&lt;/h3&gt;

&lt;p&gt;ArrayList and Hashtable predate generics in the .NET framework and store everything as the base object type. Every read requires an explicit cast, which both adds overhead and removes compile-time type checking - a type&lt;br&gt;
mismatch that should be caught immediately by the compiler instead surfaces later as a runtime InvalidCastException. Value types stored in these collections also get boxed, wrapped in an object allocated on the heap, which carries a genuine, measurable performance cost compared to a generic List storing integers directly without any boxing at all. There is no scenario in modern C# where reaching for ArrayList or Hashtable over their generic equivalents is the right engineering decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Dictionary Lookup Is Actually O(1)
&lt;/h3&gt;

&lt;p&gt;A Dictionary is backed by a hash table internally. Each key gets run through a hash function that produces a number, and that number determines which internal bucket the entry is stored in. Looking up a key means hashing it and jumping directly to the relevant bucket, rather than scanning through every stored entry in sequence - which is precisely why lookup time stays close to constant regardless of how many items the Dictionary holds.&lt;/p&gt;

&lt;p&gt;This convenience comes with one real responsibility: a custom class used as a Dictionary key must correctly override both GetHashCode and Equals, consistently with each other. Get this wrong, and lookups fail in confusing, silent ways - entries land in unexpected buckets, or two objects that should be treated as equal are instead treated as entirely different keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Default to List unless there is a specific, identifiable reason to reach for something else - it remains correct for the large majority of real-world cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reach for Dictionary the moment looking something up by an identifier becomes a frequent operation - the jump from O(n) to O(1) is a real, measurable difference at any meaningful scale. Use HashSet for uniqueness guarantees and set operations rather than manually checking Contains on a List inside a loop.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Accept IEnumerable in method parameters specifically when a method only needs to iterate once - it keeps an API's surface flexible for whatever callers want to pass in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return concrete types like List when callers genuinely need the stronger guarantees a concrete type provides, rather than hiding behind an abstraction that gives them less to work with.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never reach for the legacy non-generic collections in new code - there is no remaining scenario in modern C# where they represent the better engineering choice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a custom class is ever used as a Dictionary key, both GetHashCode and Equals must be overridden correctly and consistently, or lookups will fail in ways that are genuinely difficult to debug after the fact. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Generics make it possible to write a single class or method that works correctly and safely across many different types, without duplicating code and without giving up compile-time type checking along the way. Collections are the most common, most practical application of that underlying idea - List, Dictionary, HashSet, Queue, and Stack are all generic containers shaped deliberately for different access patterns.  Knowing which one genuinely fits a given problem, and being able to&lt;br&gt;
explain why, is one of the most consistently tested pieces of C# knowledge in technical interviews, precisely because it is also one of the most consistently used pieces of knowledge in real production code.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=csharp-generics-collections-explained" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=csharp-generics-collections-explained&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More from TechStack Blog, by category:&lt;br&gt;
C# / .NET: &lt;a href="https://www.techstackblog.com/category.html?cat=csharp" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=csharp&lt;/a&gt;&lt;br&gt;
Azure: &lt;a href="https://www.techstackblog.com/category.html?cat=azure" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=azure&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow for weekly posts on C#, Azure, and cloud engineering.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>interview</category>
      <category>programming</category>
    </item>
    <item>
      <title>Azure Table Storage: The Middle Ground Between a SQL Table and a Document Database</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:01:28 +0000</pubDate>
      <link>https://dev.to/manoharij/azure-table-storage-the-middle-ground-between-a-sql-table-and-a-document-database-2262</link>
      <guid>https://dev.to/manoharij/azure-table-storage-the-middle-ground-between-a-sql-table-and-a-document-database-2262</guid>
      <description>&lt;p&gt;I published a comparison of Azure SQL, Cosmos DB, and Blob Storage a little while back. Looking back at it, Azure Table Storage was a real gap - it sits in a genuinely useful middle position between a relational table and a full document database, and it deserves its own proper explanation rather than a passing mention. This post fills that in, with real C# code and a few lessons from using it in production alongside Azure Service Bus at Blue Yonder.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Azure Table Storage Actually Is
&lt;/h2&gt;

&lt;p&gt;Azure Table Storage is a NoSQL key-value store for large amounts of structured, non-relational data. It occupies an interesting middle position - simpler and considerably cheaper than Cosmos DB, while still being schema-flexible in a way Azure SQL fundamentally is not. Despite the name, it has nothing to do with SQL tables in the relational sense - "table" here is closer to a giant, partitioned dictionary than a database table with fixed columns.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Azure SQL&lt;/strong&gt;&lt;/em&gt; is a filing cabinet with identical forms in every folder. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Cosmos DB&lt;/strong&gt;&lt;/em&gt; is a box of index cards where every card can say something different. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Table Storage&lt;/strong&gt;&lt;/em&gt; sits somewhere in between - a long row of labeled shoeboxes on a shelf, where each shoebox holds many index cards, and every card in a shoebox can still carry different fields, but things get found quickly specifically because you know which shoebox to check first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Table Storage Actually Sits
&lt;/h2&gt;

&lt;p&gt;Against Azure SQL, Table Storage trades real relational power and joins for dramatically lower cost and a flexible schema per entity. &lt;br&gt;
Against Cosmos DB, it trades rich query capability and global distribution for an even simpler pricing model and operational footprint. It is genuinely the cheapest of the three options, and the one with the most limited query language by a meaningful margin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Concepts
&lt;/h2&gt;

&lt;p&gt;A &lt;em&gt;Table&lt;/em&gt; is a collection of entities - schema-flexible in spirit, similar to a Cosmos DB container, but considerably simpler in practice. &lt;br&gt;
An &lt;em&gt;Entity&lt;/em&gt; is a single row-like record, identified by a PartitionKey and a RowKey, plus any number of additional properties beyond those two required fields. &lt;br&gt;
The &lt;em&gt;PartitionKey&lt;/em&gt; groups related entities together for fast lookup and horizontal scalability - exactly the same kind of design decision as a partition key in Cosmos DB, and just as consequential if chosen poorly. The &lt;em&gt;RowKey&lt;/em&gt; uniquely identifies an entity within its specific partition.&lt;/p&gt;

&lt;p&gt;Together, PartitionKey and RowKey form an entity's complete unique identifier within the table.&lt;/p&gt;

&lt;p&gt;A practical example makes the flexibility clear: two log entities sharing the same partition, representing the same month, can still carry entirely different fields if a logging format changes over time - no schema migration required, the same flexibility spirit as Cosmos DB, at a fraction of the operating cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading and Writing From C
&lt;/h2&gt;

&lt;p&gt;After installing the Azure.Data.Tables NuGet package, working with Table Storage from C# follows a straightforward pattern: construct a TableClient pointed at a specific table, then read and write TableEntity objects using their PartitionKey and RowKey.&lt;/p&gt;

&lt;p&gt;A point read - fetching one specific entity by its exact PartitionKey and RowKey - is the fastest possible operation the service offers, conceptually identical to a point read in Cosmos DB. Querying within a single partition using a filter expression remains fast and well-supported. Updates and deletes follow the same PartitionKey-plus-RowKey addressing scheme throughout.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Query Language, and Its Real Limitation
&lt;/h2&gt;

&lt;p&gt;This is where Table Storage is honest about being the cheapest, simplest option of the three. There is no SQL-like query language the way Cosmos DB offers one. Filtering uses OData-style string expressions - reasonably readable for simple conditions, but with no joins, no aggregates, and no server-side grouping available at all.&lt;/p&gt;

&lt;p&gt;A filter checking for entities in a specific partition with a specific status level reads naturally enough as an OData expression,and produces broadly similar results to the equivalent Azure SQL or Cosmos DB query for that simple case. The difference becomes obvious the moment a query needs a join, an aggregate like a count or an average, or needs to span many partitions efficiently. Table Storage simply does not offer that capability - querying happens within a partition, or by scanning across all partitions, with nothing in between.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why The Limitation Is Actually The Point
&lt;/h2&gt;

&lt;p&gt;Table Storage deliberately trades query power for extreme low cost and high throughput on simple lookups. It is the right tool specifically when the access pattern is almost always "give me everything in partition X" or "give me the one entity at this exact PartitionKey and RowKey combination" - when joins, aggregates, and complex filtering are simply not needed, when data volume runs into the millions of records but the logic per record stays simple, and when cost matters more than query flexibility.&lt;/p&gt;

&lt;p&gt;Real-world scenarios that fit this profile well include application logging and audit trails, IoT telemetry keyed by device identifier and timestamp, session state keyed by session identifier, simple lookup caches, and a message or event audit trail running alongside a service like Azure Service Bus - logging every message processed, keyed by date and message identifier, without needing the query power or cost of a full database to answer a simple question like "did this message get processed, and when." At Blue Yonder, Table Storage and Service Bus worked together in exactly this way, with Service Bus handling actual message delivery while Table Storage held a lightweight, low-cost record of what had moved through the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Genuine Naming Trap Worth Knowing
&lt;/h2&gt;

&lt;p&gt;Confusingly, Cosmos DB also offers its own "Table API" that uses the exact same entity model - PartitionKey and RowKey - and is largely code-compatible with standalone Table Storage. Azure Table Storage, the storage-account-based service covered in this post, remains the cheapest option, with no throughput SLA beyond standard storage account limits and the simplest possible pricing model. Cosmos DB's Table API uses the identical programming model but runs on Cosmos DB's underlying infrastructure - global distribution, guaranteed throughput, multi-region replication - at Cosmos DB pricing.&lt;/p&gt;

&lt;p&gt;The practical migration path this enables is genuinely useful: start on plain Table Storage for cost efficiency, and if global distribution or guaranteed low-latency service levels become necessary later, migrate to Cosmos DB's Table API with minimal code changes, since the entity model is shared between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  No native recovery - know this before you rely on it
&lt;/h2&gt;

&lt;p&gt;Table Storage has no built-in soft-delete or point-in-time restore for individual entities - once deleted, an entity is gone immediately. The common production workaround is to never actually delete: add an IsDeleted flag and update it instead, or pair the table with a scheduled export for backup. This is a real limitation, and it is part of why Table Storage fits append-heavy, rarely-deleted data (logs, telemetry, audit trails) far better than it fits data where deletion and recovery are routine operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Honest Three-Way Comparison
&lt;/h2&gt;

&lt;p&gt;Choose Azure SQL when data is relational, real joins and aggregates are genuinely needed, and the data shape stays consistent across records.&lt;br&gt;
Choose Table Storage when data volume is high and the access pattern is simple - point lookups or partition scans - and cost matters more than&lt;br&gt;
query flexibility. &lt;br&gt;
Choose Cosmos DB when document shape varies meaningfully between records, real query power over JSON structure is needed, or global distribution and guaranteed low-latency reads are genuine requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Table Storage and Cosmos DB share an entity model but are priced and operated in completely different ways - know precisely which one is actually being provisioned before committing to either.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The OData filter syntax is genuinely limited. If a query needs a join or an aggregate, Table Storage is the wrong tool for that job regardless of how attractive its cost looks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PartitionKey design matters here exactly as much as it does in Cosmos DB get it wrong and partition scans become expensive as data grows.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Table Storage is frequently the right default first choice for logging and telemetry data specifically because that access pattern is almost always simple key-based lookups rather than anything more complex.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pairing it with a messaging service like Service Bus for a lightweight audit trail is a genuinely useful pattern worth knowing, not just a theoretical one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Azure Table Storage fills the real gap between a relational table and a full document database - schema-flexible in the same spirit as Cosmos DB, but priced and queried far more simply, with genuine limitations on what it can be asked to do. It earns its place specifically when the access pattern is simple and the volume is high. It was a gap worth closing properly, with real production context behind it rather than just a theoretical comparison.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=azure-table-storage-explained" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=azure-table-storage-explained&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Related reading on TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=azure-cosmos-db-blob-storage" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=azure-cosmos-db-blob-storage&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More from TechStack Blog, by category:&lt;br&gt;
Azure: &lt;a href="https://www.techstackblog.com/category.html?cat=azure" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=azure&lt;/a&gt;&lt;br&gt;
Database: &lt;a href="https://www.techstackblog.com/category.html?cat=database" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=database&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow for weekly posts on Azure, C#, and cloud engineering.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>nosql</category>
      <category>dotnet</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Azure Cosmos DB and Blob Storage: When SQL Is Not the Right Tool</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Wed, 24 Jun 2026 16:35:38 +0000</pubDate>
      <link>https://dev.to/manoharij/azure-cosmos-db-and-blob-storage-when-sql-is-not-theright-tool-cop</link>
      <guid>https://dev.to/manoharij/azure-cosmos-db-and-blob-storage-when-sql-is-not-theright-tool-cop</guid>
      <description>&lt;p&gt;Every post on my blog so far has used Azure SQL - a relational database with a fixed schema, queried with standard SQL. That has been the right choice for the blog's Posts table, which is tabular and consistent in shape. But not every kind of data fits neatly into rows and fixed columns, and not every kind of content belongs in a database row at all. At Blue Yonder, I used Cosmos DB to hold operational and application data from an Azure-based integration platform, querying it with KQL to support troubleshooting and reporting - a genuinely different use case from a primary transactional database, and a good illustration of just how flexible the document model can be. This post covers the two Azure services worth reaching for when SQL genuinely is not the right fit: &lt;em&gt;&lt;strong&gt;Cosmos DB&lt;/strong&gt;&lt;/em&gt; for flexible, fast-changing JSON documents, and &lt;em&gt;&lt;strong&gt;Blob Storage&lt;/strong&gt;&lt;/em&gt; for files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Azure Cosmos DB Actually Is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Azure Cosmos DB&lt;/em&gt;&lt;/strong&gt; is a fully managed NoSQL database built for global distribution and elastic scale. Instead of rows constrained to fixed columns, it stores JSON documents - and critically, each document can have a different shape from the next, even within the same container. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Azure SQL&lt;/em&gt;&lt;/strong&gt; is a filing cabinet with labeled folders and a fixed form inside each one - every form has the same fields in the same order, every single time. Cosmos DB is closer to a box of index cards, where each card can hold completely different information structured however makes sense for that one card, while still letting you flip through the whole box quickly when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Concrete Comparison Using This Blog's Own Data
&lt;/h2&gt;

&lt;p&gt;The Posts table in Azure SQL has a fixed set of columns - Id, Title, Slug, Tech, and so on. Every row shares exactly the same structure. Adding a genuinely new field - say, an array of structured code blocks with language tags - means altering the table schema for every single row, even posts that will never use that field.&lt;/p&gt;

&lt;p&gt;The same data reshaped as a Cosmos DB document removes that constraint entirely. One document might carry a codeBlocks array with language and code properties. A different document might instead carry a relatedServices array and a diagramUrl pointing at an image. Neither document needs to match the other's shape, and no schema migration is required to introduce a field that only some documents will ever use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Cosmos DB Concepts
&lt;/h2&gt;

&lt;p&gt;An &lt;em&gt;Account&lt;/em&gt; is the top-level Cosmos DB resource - a single account can actually support multiple APIs, including NoSQL, MongoDB, Cassandra, Gremlin, and Table, though the NoSQL API is the most commonly used and the one covered here. &lt;br&gt;
A &lt;em&gt;Database&lt;/em&gt; is a logical grouping within an account, conceptually similar to a SQL database. &lt;br&gt;
A &lt;em&gt;Container&lt;/em&gt; is roughly the document-world equivalent of a SQL table, but with no fixed schema enforced across the documents inside it. &lt;br&gt;
An &lt;em&gt;Item&lt;/em&gt;, or &lt;em&gt;document&lt;/em&gt;, is a single JSON object inside a container - roughly equivalent to a row, but with a flexible shape.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;Partition Key&lt;/em&gt; deserves its own emphasis, because it is genuinely the single most consequential design decision in Cosmos DB. It is a property within your document used to distribute data across physical partitions for scale. Choosing poorly does not just create a minor inefficiency - it can cause real performance problems as data grows, because all writes for a given partition key value land on the same physical partition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Partition Key
&lt;/h2&gt;

&lt;p&gt;For a hypothetical Posts container, several candidates illustrate the tradeoff clearly. &lt;em&gt;Partitioning by technology&lt;/em&gt; tag works well if queries are usually filtered by technology, but risks creating a disproportionately large "Azure" partition if that tag dominates the content, which becomes a hot, oversized partition relative to the others. &lt;em&gt;Partitioning by slug&lt;/em&gt; guarantees excellent distribution since every slug is unique, but querying "all posts" still requires a fan-out across every partition, which costs more in Request Units. &lt;em&gt;Partitioning by year&lt;/em&gt; suits time-based access patterns well, but concentrates all of the current year's writes onto a single partition until the calendar turns over.&lt;/p&gt;

&lt;p&gt;The actual rule underlying all of this: pick the property that aligns with the most frequent query pattern in your application, while also spreading data roughly evenly across many distinct values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying Cosmos DB
&lt;/h2&gt;

&lt;p&gt;Cosmos DB's NoSQL API uses a query language that visually resembles SQL but operates over JSON structure rather than table columns. Anyone who already knows SQL picks this up quickly. A query like selecting title, slug, and reading time from a container, filtering where a tech array contains "Azure", and ordering by reading time descending, reads almost identically to the equivalent Azure SQL query - the syntax is genuinely that close for simple cases.&lt;/p&gt;

&lt;p&gt;Where it diverges meaningfully is querying into nested structures. Cosmos DB can join directly into an array nested inside the same document - for instance, finding every code block with a specific language tag, embedded inside posts - without needing a second table at all. The equivalent in a relational model requires an actual separate table and a real JOIN across tables, since SQL has no native concept of an array living inside a single row.&lt;/p&gt;

&lt;h2&gt;
  
  
  LINQ Against Cosmos DB
&lt;/h2&gt;

&lt;p&gt;For anyone who has used Entity Framework Core's LINQ support against Azure SQL, querying Cosmos DB from C# will feel immediately familiar at the syntax level - Where, OrderByDescending, and the rest of the standard LINQ vocabulary all work against a Cosmos DB container through its LINQ provider. The vocabulary is the same. The engine underneath is completely different - EF Core generates T-SQL against a relational engine, while Cosmos DB's LINQ provider generates its own SQL-like query against a document store with an entirely different cost and performance model. Familiar syntax does not imply equivalent execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Units
&lt;/h2&gt;

&lt;p&gt;Cosmos DB does not bill the way Azure SQL does, on compute tiers and DTUs or vCores. It bills on &lt;em&gt;Request Units&lt;/em&gt;, a normalized measure of the cost of any given operation. A simple point read by id and partition key is roughly one Request Unit - the cheapest possible operation in the system. A broader query scanning many documents can cost tens or hundreds of Request Units depending on its complexity. Writes typically cost more Request Units than an equivalent read.&lt;/p&gt;

&lt;p&gt;Two billing modes exist. &lt;em&gt;Provisioned throughput&lt;/em&gt; reserves a fixed Request Unit per second capacity that is paid for whether it is fully used or not - appropriate for predictable, steady traffic. &lt;em&gt;Serverless mode&lt;/em&gt; charges only for the Request Units actually consumed, with no fixed reservation at all - a far better fit for unpredictable, generally low-traffic workloads, which describes most personal blogs and small applications far more accurately than a fixed provisioned tier would.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Cosmos DB Is The Right Choice Over Azure SQL
&lt;/h2&gt;

&lt;p&gt;Cosmos DB earns its place when document shape varies significantly between items, when an application needs single-digit millisecond reads at genuinely global scale, when multi-region write distribution matters, when data is naturally hierarchical or nested in a way that maps cleanly to JSON, or when throughput needs to scale elastically and automatically without manual intervention.&lt;/p&gt;

&lt;p&gt;Azure SQL remains the better choice when data is naturally tabular with a consistent shape across records, when strong relational integrity through foreign keys and joins across many related tables genuinely matters, when a team already thinks fluently in SQL and the data fits that model comfortably, or when complex multi-table transactions are a common part of the workload.&lt;/p&gt;

&lt;p&gt;This blog's own Posts data is tabular and consistent in shape - Azure SQL remains the correct choice for it. Where Cosmos DB genuinely earns its place, based on real production experience, is somewhere quite different from "replace your application database": holding operational and log data from an integration platform, where one event might carry a handful of fields and another dozens, then querying that data with KQL for troubleshooting and reporting rather than serving it as primary application content. That pattern - flexible storage for fast-changing&lt;br&gt;
operational data, queried for insight rather than served to end users - is worth knowing even for engineers who never touch Cosmos DB as a primary database.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Azure Blob Storage Actually Is
&lt;/h2&gt;

&lt;p&gt;Azure Blob Storage stores unstructured files - images, videos, PDFs, backups, log files - as objects called blobs, organized inside containers. It functions as neither a traditional database nor a traditional filesystem but remains accessible over HTTP in a way that feels filesystem- like in practice.&lt;/p&gt;

&lt;p&gt;If Azure SQL and Cosmos DB are both filing systems built for structured information, Blob Storage is closer to a warehouse. There is no querying the contents of what sits inside a stored box - only knowing which box it is, after which the warehouse hands it over, whole, exactly as it was left.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blob Storage Tiers
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;Hot tier&lt;/em&gt; is optimized for frequently accessed data, carrying the highest storage cost paired with the lowest access cost - appropriate for something like images displayed on a blog's homepage that get requested constantly. The &lt;em&gt;Cool tier&lt;/em&gt; suits infrequently accessed data intended to be stored for at least thirty days, trading lower storage cost for higher access cost relative to Hot - last month's exported analytics reports fit naturally here. The &lt;em&gt;Archive tier&lt;/em&gt; suits data rarely accessed at all, intended for at least one hundred eighty days of storage, with the lowest storage cost and highest access cost of the three, plus the detail that data must be rehydrated before it can be read again, a process that can take hours - genuinely multi-year backups nobody expects to open belong here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blob Types
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Block Blobs&lt;/strong&gt;&lt;/em&gt; are the most common type, optimized for efficiently uploading large amounts of data in individually manageable blocks - the right choice for images, video, documents, and backups in the overwhelming majority of cases. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Append Blobs&lt;/strong&gt;&lt;/em&gt; are optimized specifically for append operations, allowing data to be added to the end without modifying what already exists - well suited to logging scenarios where new entries continuously accumulate. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Page Blobs&lt;/strong&gt;&lt;/em&gt; are optimized for random read and write access patterns, primarily relevant to virtual hard disk files backing virtual machines, a scenario that rarely comes up outside infrastructure-focused work.&lt;/p&gt;

&lt;h2&gt;
  
  
  SAS Tokens
&lt;/h2&gt;

&lt;p&gt;Blob containers are private by default, which is the correct default. A Shared Access Signature, or SAS token, grants temporary, narrowly scoped access to a blob or container without making the entire container public and without sharing the storage account's actual access key. A SAS token can be constrained to a specific blob, a specific permission like read-only, and a specific expiration time - a token built to expire in one hour, granting read-only access to exactly one blob, does exactly that and nothing more once it expires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Blog Would Realistically Use Blob Storage
&lt;/h2&gt;

&lt;p&gt;A few concrete, realistic use cases stand out for this specific project, none of which are implemented yet. &lt;em&gt;Post cover images&lt;/em&gt; and inline diagrams currently rely on external links rather than self-hosted assets - Blob Storage would be the natural home for self-hosted images served from a stable URL. The &lt;em&gt;Open Graph share image&lt;/em&gt; used for social previews is currently committed directly into the frontend code repository - it could instead live in Blob Storage behind a CDN, cleanly separating binary assets from application source code. Scheduled database backups, exported independently of Azure SQL's own built-in backup retention, would fit naturally in the Cool tier. And if a comments feature with image uploads is ever added, Blob Storage is precisely the right service for storing user-submitted files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Services, Side By Side
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Azure SQL&lt;/strong&gt;&lt;/em&gt; handles fixed-shape rows, queried in standard SQL, requiring a defined schema, and billed on a DTU or vCore model - the right fit for relational, structured data, which describes this blog's own Posts table accurately. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Cosmos DB&lt;/strong&gt;&lt;/em&gt; handles flexible JSON documents, queried in a SQL-like language suited to NoSQL access patterns, requiring no fixed schema, and billed on Request Units - the right fit for fast-changing documents that need global scale. &lt;br&gt;
&lt;em&gt;&lt;strong&gt;Blob Storage&lt;/strong&gt;&lt;/em&gt; handles raw files with no query language at all beyond retrieval by key, requiring no schema in the traditional sense, and billed per gigabyte stored plus per operation - the right fit for images, video, backups, and any other genuinely unstructured content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Choosing a Cosmos DB partition key is the single decision most likely to cause real performance problems later if made carelessly early on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Serverless billing mode fits unpredictable, low-to-moderate traffic far better than provisioned throughput - defaulting to provisioned out of habit, without a real reason, adds cost without a corresponding benefit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SAS tokens belong on any blob access that needs to be temporary or narrowly scoped - a fully public container should be reserved for content that is genuinely meant to be public without exception.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;LINQ syntax transfers comfortably between EF Core and Cosmos DB, but the execution engine and cost model underneath are entirely different - familiarity with the syntax should not be mistaken for familiarity with the performance characteristics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The actual skill is matching the shape of the data to the right service, not defaulting to whichever tool sounds newest or most powerful. A relational table remains exactly the right choice for plenty of real workloads, including this blog's own. And Cosmos DB's most valuable real-world use is not always "primary database" - operational and log data analysis, queried with KQL, is a pattern worth recognizing on its own.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Azure SQL fits data that is naturally tabular and relational. Cosmos DB fits data that is naturally document-shaped, fast-changing, or in need of global low-latency distribution. Blob Storage fits anything that is fundamentally a file rather than structured data at all. All three coexist comfortably within real Azure architectures - the genuine skill lies in matching the shape of the data to the right service, rather than selecting one tool and forcing every problem through it regardless of fit.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=azure-cosmos-db-blob-storage" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=azure-cosmos-db-blob-storage&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More from TechStack Blog, by category:&lt;br&gt;
Azure: &lt;a href="https://www.techstackblog.com/category.html?cat=azure" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=azure&lt;/a&gt;&lt;br&gt;
Database: &lt;a href="https://www.techstackblog.com/category.html?cat=database" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=database&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow for weekly posts on Azure, C#, and cloud engineering.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cosmosdb</category>
      <category>database</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Azure Key Vault: Where Every Secret in This Blog Actually Lives</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Mon, 22 Jun 2026 15:33:14 +0000</pubDate>
      <link>https://dev.to/manoharij/azure-key-vault-where-every-secret-in-this-blog-actually-lives-33on</link>
      <guid>https://dev.to/manoharij/azure-key-vault-where-every-secret-in-this-blog-actually-lives-33on</guid>
      <description>&lt;p&gt;I have written some version of "never hardcode secrets, store them in Key Vault instead" in at least five of my last nine posts on this blog. I never actually stopped to explain what that means in practice. This post fixes that, using the real secrets this very blog depends on: a database connection string, an admin panel password, and a set of GitHub deployment credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Azure Key Vault Actually Is
&lt;/h2&gt;

&lt;p&gt;Azure Key Vault is a managed service for storing secrets, encryption keys, and certificates securely in the cloud. Instead of a password sitting in a configuration file or a public GitHub repository where anyone with read access can see it, the password lives in Key Vault - encrypted, access-controlled, and logged every single time it is read.&lt;/p&gt;

&lt;p&gt;Picture your code as a house with see-through walls - anyone looking at the repository can see everything inside, including any password left lying on the kitchen table. Key Vault is a bank vault a few streets away. Your code does not hold the password directly; it holds a key card that lets it walk over and request the password at the exact moment it is needed. Lose the key card, and you still cannot get into the vault without proper identity verification on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Things Key Vault Stores
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Secrets&lt;/em&gt;&lt;/strong&gt; are plain string values - passwords, connection strings, API keys, tokens. &lt;br&gt;
&lt;strong&gt;&lt;em&gt;Keys&lt;/em&gt;&lt;/strong&gt; are cryptographic keys used for encrypting and decrypting data, or for signing and verifying it. &lt;br&gt;
&lt;strong&gt;&lt;em&gt;Certificates&lt;/em&gt;&lt;/strong&gt; are X.509 certificates used for TLS or client authentication between services. &lt;br&gt;
Most applications, including this one, primarily use the Secrets feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Honest Admission About TechStackBlog's Own Setup
&lt;/h2&gt;

&lt;p&gt;This blog does not actually use Key Vault directly today. The database password lives in Azure App Service Configuration. The admin panel password and deployment credentials live in GitHub Secrets. For a single-application personal project, this is a perfectly reasonable and secure setup.&lt;/p&gt;

&lt;p&gt;Key Vault earns its place once you have multiple applications that need to share the same secret, once you need automated secret rotation, or once a compliance requirement demands a complete audit trail of every single&lt;br&gt;
read. Understanding when a simpler tool is genuinely sufficient is as much a part of good engineering judgment as knowing how to use the more powerful tool when it is actually warranted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting It Up For Real
&lt;/h2&gt;

&lt;p&gt;Creating a Key Vault starts the same way as any other Azure resource - through the portal, in the same resource group as everything else. Adding a secret is a matter of giving it a name and a value under the Secrets blade.&lt;/p&gt;

&lt;p&gt;The access model is where most of the real decisions happen. The modern, recommended approach uses Azure RBAC rather than the older Access Policies model - assign the "Key Vault Secrets User" role to whatever identity needs to read secrets, rather than the much more powerful "Key&lt;br&gt;
Vault Administrator" role, which can also modify access policies and delete the vault entirely. An application that only needs to read a connection string should never hold administrator-level access.&lt;/p&gt;

&lt;p&gt;Enabling Managed Identity on the consuming App Service is the final piece - this gives the application its own identity within Azure Active Directory, with no password or client secret to manage at all on the application's side. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Code That Actually Reads a Secret
&lt;/h2&gt;

&lt;p&gt;After installing the Azure.Identity and Azure.Security.KeyVault.Secrets NuGet packages, reading a secret from C# looks like this conceptually: construct a SecretClient pointed at your vault's URL, authenticate using DefaultAzureCredential, and call GetSecretAsync with the secret's name.&lt;/p&gt;

&lt;p&gt;DefaultAzureCredential is the detail that makes this genuinely pleasant to work with rather than painful. It tries several authentication methods in sequence automatically - environment variables for CI/CD contexts, Managed Identity when running inside Azure, then falls back to your local Azure CLI or Visual Studio sign-in when running on your own machine during development. The practical result is that the exact same line of code works identically whether you are debugging locally or running in production App Service. No environment-specific branching, no "if local environment use this connection string, if production use that one."&lt;/p&gt;

&lt;p&gt;For even simpler integration, App Service Configuration supports Key Vault references directly - a special value syntax that tells App Service to resolve the actual secret value from Key Vault at startup, using its own Managed Identity, with zero Key Vault SDK code required anywhere in the application at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Versioning Matters More Than It Sounds
&lt;/h2&gt;

&lt;p&gt;Key Vault never overwrites a secret's value outright - updating a secret creates a new version while preserving every previous version with its own timestamp. Retrieving a secret without specifying a version always returns the current one, but a specific old version remains retrievable by its version identifier if ever needed.&lt;/p&gt;

&lt;p&gt;The practical benefit is a built-in safety net. Roll back to a previous secret value instantly if a rotation goes wrong. Maintain a complete history of every value a secret has ever held. Accidentally overwriting a secret with the wrong value becomes a recoverable mistake rather than a permanent one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing Who Read What, And When
&lt;/h2&gt;

&lt;p&gt;Every read of every secret can be logged automatically by enabling diagnostic settings on the vault and routing those logs into a Log Analytics workspace - the same workspace already used for Application Insights telemetry in a typical setup. From there, a KQL query against the diagnostic logs answers genuinely useful questions: did anyone access this secret outside normal business hours, which specific application is reading this secret and how frequently, and is some old deprecated application still pulling a secret it should no longer have access to at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes Worth Avoiding
&lt;/h2&gt;

&lt;p&gt;Granting Key Vault Administrator when Key Vault Secrets User would suffice is the most common over-permissioning mistake - the application almost never needs to manage access policies or delete the vault, it just needs to read values.&lt;/p&gt;

&lt;p&gt;Caching a secret value in application memory indefinitely with no refresh mechanism means that if the underlying secret rotates, the application keeps using the stale value until its next restart. Either set a reasonable cache expiry explicitly or rely on Key Vault references in App Service Configuration, which handle refresh automatically without any extra code.&lt;/p&gt;

&lt;p&gt;Storing ordinary, non-sensitive configuration values in Key Vault is a subtler mistake - Key Vault has per-operation rate limits and an associated cost, and a simple page size or feature flag does not need the protection a real secret does. Reserve Key Vault for things that genuinely need secrecy.&lt;/p&gt;

&lt;p&gt;Finally, Key Vault has soft-delete enabled by default, meaning deleted secrets remain recoverable for a retention period rather than disappearing instantly. This is usually a helpful safety net, but it also means a vault name cannot always be reused immediately after deletion during that window - worth knowing before it causes unexpected confusion during a rebuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Azure Key Vault exists so that passwords, connection strings, and certificates never have to sit directly inside application code or a public repository. Managed Identity removes the need to manage a credential purely to access your other credentials. Versioning provides a built-in safety net. Diagnostic logging provides real visibility into who is reading what.&lt;/p&gt;

&lt;p&gt;None of this is mandatory for every project from day one - but understanding it properly means being able to make a deliberate, informed choice about when a simpler option like App Service Configuration is genuinely enough, and when it is time to bring in the vault.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=azure-key-vault-secrets-management" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=azure-key-vault-secrets-management&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More from TechStack Blog, by category:&lt;br&gt;
Azure: &lt;a href="https://www.techstackblog.com/category.html?cat=azure" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=azure&lt;/a&gt;&lt;br&gt;
C# / .NET: &lt;a href="https://www.techstackblog.com/category.html?cat=csharp" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=csharp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow for weekly posts on Azure, C#, and cloud engineering.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>security</category>
      <category>dotnet</category>
      <category>cloud</category>
    </item>
    <item>
      <title>C# Async/Await and Delegates/Events: The Concepts Behind Every Responsive Application</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Fri, 19 Jun 2026 16:27:37 +0000</pubDate>
      <link>https://dev.to/manoharij/c-asyncawait-and-delegatesevents-the-concepts-behind-every-responsive-application-2k6p</link>
      <guid>https://dev.to/manoharij/c-asyncawait-and-delegatesevents-the-concepts-behind-every-responsive-application-2k6p</guid>
      <description>&lt;p&gt;Every endpoint in the C# API powering my techstackblog uses async/await. Every LINQ query I write is secretly built on delegates. These two concepts are usually taught in separate chapters, but in real production code they constantly overlap - an event handler is often async, and a delegate parameter is often awaited.&lt;/p&gt;

&lt;p&gt;This post covers both, with the analogies that made them click for me and the mistakes I see most often in production code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1 — Async and Await
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Analogy
&lt;/h3&gt;

&lt;p&gt;Imagine ordering food at a restaurant. &lt;/p&gt;

&lt;p&gt;A synchronous waiter takes your order, walks to the kitchen, and stands there waiting for your food to finish cooking - completely blocked from serving any other table the entire time.&lt;/p&gt;

&lt;p&gt;An asynchronous waiter takes your order, hands it to the kitchen, and immediately goes to serve other tables. When the kitchen finishes, the waiter is notified and brings the food over. One waiter handles many tables efficiently instead of freezing on one slow task.&lt;/p&gt;

&lt;p&gt;async/await in C# is that asynchronous waiter. The thread handling your request is not blocked waiting on a slow database call - it gets released to handle other work and resumes once the data is ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem It Actually Solves
&lt;/h3&gt;

&lt;p&gt;Without async, a thread that calls a database query sits completely idle for the duration of that call - often 100-300 milliseconds for typical queries. Under load, every concurrent request needs its own thread sitting idle, and you run out of threads fast.&lt;/p&gt;

&lt;p&gt;With async, the same thread that started the database call gets returned to the pool immediately after calling await. It goes and handles a different request. When the database responds, the original method resumes  often on a different thread entirely. The result is the same hardware handling dramatically more concurrent requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Syntax
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;async&lt;/em&gt; keyword marks a method as asynchronous. The await keyword pauses execution at that point without blocking the thread - it's pausing the method, not the thread. Task represents work that completes at some&lt;br&gt;
point in the future.&lt;/p&gt;

&lt;p&gt;A typical async method looks like this: mark the method with async, return Task or Task of some type, and use await wherever you call another async method inside it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task vs Task&amp;lt;T&amp;gt; vs async void
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Task&lt;/em&gt;&lt;/strong&gt; represents async work with no return value - just a signal of completion. &lt;em&gt;&lt;strong&gt;Task&amp;lt;T&amp;gt;&lt;/strong&gt;&lt;/em&gt; represents async work that eventually produces a value of type T. Both of these can be awaited, which is essential.&lt;/p&gt;

&lt;p&gt;async void should only be used for actual event handlers - UI button click handlers in WPF or WinForms, for example, which require that exact signature. Outside of event handlers, avoid async void entirely. You cannot await it, which means you cannot know when it completes or catch exceptions it throws. Those exceptions can crash the entire process instead of being caught gracefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Multiple Async Operations
&lt;/h3&gt;

&lt;p&gt;Sequential awaiting runs operations one after another - if you await three independent calls that each take 100ms, the total wait is 300ms even though none of them depend on each other.&lt;/p&gt;

&lt;p&gt;Task.WhenAll runs them concurrently instead. Start all three without awaiting individually, then await Task.WhenAll on all of them together. The total wait becomes roughly the duration of the slowest single operation, not the sum of all three. For any independent async operations, this is a significant and easy performance win.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Async Mistakes
&lt;/h3&gt;

&lt;p&gt;The most common mistake is forgetting await entirely - calling an async method without awaiting it returns the Task object itself rather than the data inside it, which is rarely what you want and is an easy typo to miss.&lt;/p&gt;

&lt;p&gt;The most dangerous mistake is calling .Result or .Wait() on a Task to force synchronous behavior. This can cause deadlocks in certain contexts, particularly in older ASP.NET applications, and always defeats the entire&lt;br&gt;
purpose of using async in the first place. The fix is simple: await all the way up the call stack rather than blocking partway through.&lt;/p&gt;

&lt;p&gt;A subtler mistake is wrapping something in async unnecessarily when there's no actual asynchronous work happening inside - this adds overhead with no benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2 — Delegates and Events
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Analogy
&lt;/h3&gt;

&lt;p&gt;A delegate is like a job posting that describes the exact shape of work needed - "must accept two numbers, must return one number" - without specifying who fills that role. Any method matching that shape can be&lt;br&gt;
plugged in interchangeably.&lt;/p&gt;

&lt;p&gt;An event is like a newsletter subscription. The newsletter publisher doesn't know or care who has subscribed. It simply announces "new issue ready" and every subscriber gets notified automatically. The publisher and its subscribers are completely decoupled from each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a Delegate Actually Is
&lt;/h3&gt;

&lt;p&gt;A delegate is a type-safe reference to a method. You define the delegate type once, describing the parameters and return type a matching method must have. Then any method with that exact signature can be&lt;br&gt;
assigned to a variable of that delegate type and invoked through it.&lt;/p&gt;

&lt;p&gt;In practice, you almost never declare custom delegate types in modern C#. Three built-in generic delegates cover the overwhelming majority of real-world use:&lt;br&gt;
&lt;em&gt;Func&lt;/em&gt; represents a delegate with a return value, &lt;em&gt;Action&lt;/em&gt; represents one with no return value, and &lt;em&gt;Predicate&lt;/em&gt; always returns a boolean and is typically used for filtering conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multicast Delegates
&lt;/h3&gt;

&lt;p&gt;A single delegate variable can reference multiple methods at once using the += operator. When invoked, every attached method runs in the order they were added. This is genuinely useful for scenarios like logging the same message to multiple destinations - console, file, and a telemetry service - by combining three separate Action delegates into one.&lt;/p&gt;

&lt;h3&gt;
  
  
  What an Event Actually Is
&lt;/h3&gt;

&lt;p&gt;An event is essentially a delegate with restricted access. The class that declares the event can raise it, but nothing outside that class can raise it directly - external code can only subscribe or unsubscribe handlers&lt;br&gt;
using += and -=. This protects the publishing class's control over exactly when the notification fires, which is the core difference between a plain delegate field and a proper event.&lt;/p&gt;

&lt;p&gt;The standard pattern uses EventHandler or EventHandler&amp;lt;T&amp;gt; as the delegate type, where T is a custom class deriving from EventArgs that carries whatever data subscribers need. Raising the event uses the null-conditional operator before Invoke, which gracefully does nothing if no one has subscribed rather than throwing a null reference exception.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Events Instead of Direct Method Calls
&lt;/h3&gt;

&lt;p&gt;Without events, a publishing class needs to know about and directly call every interested party - adding a new subscriber means editing the publisher's code to add another direct call. This tightly couples the publisher to every single consumer of its notifications.&lt;/p&gt;

&lt;p&gt;With events, the publisher simply raises the event and has zero knowledge of who is listening or how many subscribers exist. Adding a new subscriber is just one line of subscription code added externally - the publisher class itself never changes. This is loose coupling in its purest practical form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Events Combined With Async
&lt;/h3&gt;

&lt;p&gt;In real production systems, event handlers are frequently async themselves - an event fires, and the handler needs to do asynchronous work like sending an alert through an external API. This is one of the rare legitimate contexts for async void, since event handler delegate signatures typically can't be changed to return Task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where You're Already Using Delegates Without Realizing It
&lt;/h3&gt;

&lt;p&gt;Every LINQ method that takes a lambda is actually accepting a delegate parameter. Where takes a Func&amp;lt;T, bool&amp;gt; - a delegate that takes one item and returns true or false. Select takes a Func&amp;lt;T, TResult&amp;gt; - a delegate that transforms one item into another shape. OrderBy takes a delegate that extracts the sort key. Every single LINQ chain you write is built entirely on the delegate pattern, even though it rarely feels that way while writing it.&lt;/p&gt;

&lt;p&gt;ASP.NET Core middleware is built on the same foundation - the next parameter passed into each middleware delegate is itself a delegate pointing to the next piece of middleware in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons From Production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Always await all the way up the call stack. Never call .Result or .Wait() on a Task anywhere in application code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Task.WhenAll whenever you have multiple independent async operations it's an easy, safe performance improvement that's frequently overlooked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prefer the built-in Func, Action, and Predicate delegate types over declaring custom delegate types. Custom delegates are rarely necessary in modern C#.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the null-conditional invoke pattern when raising events - it prevents a null reference exception in the common case where no one has subscribed yet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reserve async void exclusively for actual event handler signatures. Everywhere else, use async Task so exceptions propagate correctly and the work can be awaited.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recognizing that every LINQ lambda is secretly a delegate makes the entire LINQ API click faster - it stops feeling like special syntax and starts feeling like an extension of something you already understand.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Async/await keeps applications responsive by releasing threads during slow operations instead of blocking them, allowing far more concurrent work on the same hardware.&lt;br&gt;
Delegates provide type-safe references to methods, letting behavior be passed around as data. Events build on delegates to create loosely coupled systems where publishers and subscribers never need direct knowledge&lt;br&gt;
of each other.&lt;/p&gt;

&lt;p&gt;Together, these two concepts are the backbone of responsive, maintainable C# applications - showing up everywhere from REST API controllers to UI event handlers to the LINQ queries written dozens of times a&lt;br&gt;
day without a second thought.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=csharp-async-delegates-events-deep-dive" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=csharp-async-delegates-events-deep-dive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More from the same blog, organized by topic:&lt;/p&gt;

&lt;p&gt;Azure: &lt;a href="https://www.techstackblog.com/category.html?cat=azure" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=azure&lt;/a&gt;&lt;br&gt;
C# / .NET: &lt;a href="https://www.techstackblog.com/category.html?cat=csharp" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=csharp&lt;/a&gt;&lt;br&gt;
Web fundamentals: &lt;a href="https://www.techstackblog.com/category.html?cat=web" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=web&lt;/a&gt;&lt;br&gt;
Database: &lt;a href="https://www.techstackblog.com/category.html?cat=database" rel="noopener noreferrer"&gt;https://www.techstackblog.com/category.html?cat=database&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow for weekly posts on C#, Azure integration, and cloud engineering.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>CI/CD, YAML, Azure DevOps and Merge Conflicts: Everything I Learned Deploying to Azure</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Wed, 17 Jun 2026 17:11:02 +0000</pubDate>
      <link>https://dev.to/manoharij/cicd-yaml-azure-devops-and-merge-conflictseverything-i-learned-deploying-to-azure-5cek</link>
      <guid>https://dev.to/manoharij/cicd-yaml-azure-devops-and-merge-conflictseverything-i-learned-deploying-to-azure-5cek</guid>
      <description>&lt;p&gt;My first production deployment at Blue Yonder was entirely manual. Copy files to the server. Update the config by hand. Restart the service. Call the team to verify it was live. The whole process took 45 minutes, produced a slightly different result every time depending on who ran it, and only two people on the team knew all the steps.&lt;/p&gt;

&lt;p&gt;Then we moved to automated pipelines. The same deployment became: push code to Git, wait 4 minutes, done. Same result every single time. Anyone on the team could trigger it. Full audit log of every deployment. Rollback in one command.&lt;/p&gt;

&lt;p&gt;CI/CD is not a nice-to-have for serious software delivery. It is the foundation. This post covers everything — YAML syntax, GitHub Actions, Azure DevOps, deployment strategies, merge conflicts, and the security practices that keep pipelines safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI vs CD — The Actual Difference
&lt;/h2&gt;

&lt;p&gt;Most people use CI/CD as one term without knowing where one ends and the other begins.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continuous Integration&lt;/em&gt; is about merging code frequently and automatically verifying it. Every time a developer pushes code, the CI system builds it and runs tests. The goal is to catch problems immediately — within minutes of the bad code being merged, not days later when someone manually tests the feature.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Continuous Deployment&lt;/em&gt; takes the verified build from CI and automatically deploys it to one or more environments. Continuous Delivery deploys automatically to staging but requires a human to approve the production deployment. Continuous Deployment goes all the way to production automatically with no human approval.&lt;/p&gt;

&lt;p&gt;A complete pipeline looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code pushed -&amp;gt; Build -&amp;gt; Unit tests -&amp;gt; Integration tests
-&amp;gt; Deploy to dev -&amp;gt; Deploy to staging -&amp;gt; Manual approval
-&amp;gt; Deploy to production.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TechStack Blog uses a simplified version of this — every push to main builds the C# API, publishes it, and deploys to Azure App Service automatically. No staging environment for a solo blog project, but the same principles apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  YAML — The Language of Pipelines
&lt;/h2&gt;

&lt;p&gt;YAML (Yet Another Markup Language) is the format used by GitHub Actions, Azure DevOps, and almost every modern CI/CD tool. It uses indentation to show structure and is readable as plain text.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;critical rule&lt;/em&gt;: indentation must be consistent and use spaces, never tabs. One wrong indent and the pipeline fails with a cryptic error.&lt;/p&gt;

&lt;p&gt;The basic structure of any pipeline has three parts.&lt;br&gt;
The &lt;strong&gt;&lt;em&gt;trigger&lt;/em&gt;&lt;/strong&gt; defines when the pipeline runs — on push, on pull request, on a schedule, or manually. &lt;br&gt;
The &lt;strong&gt;&lt;em&gt;jobs&lt;/em&gt;&lt;/strong&gt; define what machines do the work and in what order.&lt;br&gt;
The &lt;strong&gt;&lt;em&gt;steps&lt;/em&gt;&lt;/strong&gt; define the individual commands each machine runs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Lists&lt;/em&gt; use a dash at the start of each item. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Objects&lt;/em&gt; use key-value pairs with a colon. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Nested structure&lt;/em&gt; uses consistent indentation. &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Secrets and variables&lt;/em&gt; are referenced with double curly braces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding these four rules lets you read any YAML pipeline file regardless of which CI/CD platform wrote it.&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;p&gt;GitHub Actions is CI/CD built directly into GitHub. Every workflow is a YAML file stored in the &lt;code&gt;.github/workflows/&lt;/code&gt; folder of your repository. When the trigger condition is met — a push, a PR, a schedule — GitHub spins up a fresh virtual machine, runs your steps, and tears the machine down when done.&lt;/p&gt;

&lt;p&gt;The TechStack Blog API deployment workflow does eight things in sequence. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It checks out the repository code.&lt;/li&gt;
&lt;li&gt;It installs the .NET 8 SDK. &lt;/li&gt;
&lt;li&gt;It restores NuGet packages.&lt;/li&gt;
&lt;li&gt;It builds the C# project in Release mode. &lt;/li&gt;
&lt;li&gt;It runs tests.&lt;/li&gt;
&lt;li&gt;It publishes the output to a folder. &lt;/li&gt;
&lt;li&gt;It zips that folder.&lt;/li&gt;
&lt;li&gt;It logs into Azure using service principal credentials stored as GitHub Secrets and deploys the zip to Azure App Service.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire process takes 3-4 minutes. Every push to main triggers it automatically.&lt;/p&gt;

&lt;p&gt;The most important concept in GitHub Actions is job dependencies. The deploy job has "needs: build" which means it only runs if the build job succeeds. If the build fails the deployment never happens. This prevents&lt;br&gt;
broken code from reaching Azure.&lt;/p&gt;

&lt;p&gt;Secrets in GitHub Actions are stored encrypted and never appear in logs. They are referenced with the &lt;code&gt;${{ secrets.SECRET_NAME }}&lt;/code&gt; syntax. The Azure service principal credentials — client ID, tenant ID, and subscription ID — are stored as secrets so they never appear in the YAML file that is publicly visible in the repository.&lt;/p&gt;
&lt;h2&gt;
  
  
  Azure DevOps Pipelines
&lt;/h2&gt;

&lt;p&gt;Azure DevOps is Microsoft's enterprise CI/CD platform. It has more features than GitHub Actions and is the standard in large organizations. At Blue Yonder, Azure DevOps managed our integration platform deployments&lt;br&gt;
across multiple environments with formal approval workflows.&lt;/p&gt;

&lt;p&gt;The key difference from GitHub Actions is the concept of stages with environments. You define a Production environment in Azure DevOps and configure it to require approval from specific people before any deployment can proceed. When the pipeline reaches the production stage, it pauses and sends an email to the approvers. Only after someone clicks &lt;em&gt;Approve&lt;/em&gt; does the deployment continue.&lt;/p&gt;

&lt;p&gt;This is the approval gate pattern — critical for any system where a bad deployment has real consequences. GitHub Actions has similar functionality through Environment protection rules, but Azure DevOps makes it more prominent and easier to configure for teams.&lt;/p&gt;

&lt;p&gt;Azure DevOps also has variable groups — shared sets of variables that multiple pipelines can reference. Instead of duplicating the same connection string in ten different pipeline files, you define it once in&lt;br&gt;
a variable group and all pipelines inherit it. When the value changes, you update it in one place.&lt;/p&gt;
&lt;h2&gt;
  
  
  Manual vs Automated Deployment
&lt;/h2&gt;

&lt;p&gt;Automated deployment is the goal but manual deployment has its place. Know when to use each.&lt;/p&gt;

&lt;p&gt;Automate everything that runs repeatedly and must produce identical results every time. Application code, configuration changes, dependency updates — these all belong in automated pipelines.&lt;/p&gt;

&lt;p&gt;Keep manual control over things that require judgment. Database schema migrations that could lose data. First-time infrastructure provisioning with Terraform or Bicep. Emergency hotfixes that are faster to deploy manually than waiting for a pipeline. Deployments that require physically touching hardware.&lt;/p&gt;

&lt;p&gt;The hybrid approach that works in practice: code deployments are fully automated, database migrations are automated with a manual approval step, infrastructure changes are manual with documentation, and production releases require a pipeline approval gate even though the deployment itself is automated.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deployment Strategies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Blue-Green deployment&lt;/em&gt;&lt;/strong&gt; runs two identical environments. Traffic goes to the blue environment (live). You deploy to the green environment (idle) and test it. When ready, you switch all traffic to green in seconds. Blue becomes the instant rollback target. Azure App Service deployment slots implement this pattern directly — swap slots to go live, swap back to roll back.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Canary deployment&lt;/strong&gt;&lt;/em&gt; sends a small percentage of traffic to the new version first. Start at 5%, monitor for errors, increase to 25%, then 50%, then 100%. If errors appear at any stage, roll back the canary percentage.&lt;br&gt;
Real users test the new version but only a small fraction of them are exposed to any problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Rolling deployment&lt;/em&gt;&lt;/strong&gt; updates one server at a time while the others keep serving traffic. No downtime but slower to roll back — you have to wait for the rolling update to complete in reverse.&lt;/p&gt;

&lt;p&gt;For a &lt;em&gt;&lt;strong&gt;single-server deployment&lt;/strong&gt;&lt;/em&gt; like TechStack Blog, the simplest strategy is stop, replace, start. Acceptable for a blog. Not acceptable for a system that needs zero downtime.&lt;/p&gt;
&lt;h2&gt;
  
  
  Merge Conflicts
&lt;/h2&gt;

&lt;p&gt;A merge conflict happens when two developers change the same line of the same file and Git cannot automatically decide which change to keep. This is the moment most new developers dread. Once you understand it, conflicts&lt;br&gt;
are completely manageable.&lt;/p&gt;

&lt;p&gt;The scenario: main branch has a function returning "Hello". Developer A changes it to "Hello World" and merges first. Developer B changes the same line to "Hi there" and tries to merge. Git sees both changed the same line and does not know which version is correct. Git marks the file and asks you to decide.&lt;/p&gt;

&lt;p&gt;What Git puts in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
&lt;/span&gt;&lt;span class="p"&gt;your version of the code here
&lt;/span&gt;&lt;span class="gh"&gt;=======
&lt;/span&gt;&lt;span class="p"&gt;their version of the code here
&lt;/span&gt;&lt;span class="gi"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; their-branch-name
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything between the less-than signs and the equals signs is your version. Everything between the equals signs and the greater-than signs is their version.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;To resolve&lt;/em&gt;: delete both markers, keep whichever code is correct (or combine both), save the file, run git add on the resolved file, then git commit to complete the merge.&lt;/p&gt;

&lt;p&gt;VS Code shows conflict markers with clickable buttons — Accept Current Change, Accept Incoming Change, Accept Both Changes. Click the appropriate button, VS Code removes the markers automatically, save and commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing Merge Conflicts
&lt;/h2&gt;

&lt;p&gt;Pull before you push every single time. This one habit eliminates the majority of conflicts by keeping your local branch close to the current state of main.&lt;/p&gt;

&lt;p&gt;Commit small and commit often. Large commits that change many files across many features create maximum overlap with other developers' work. Small focused commits targeting specific files create minimum overlap.&lt;/p&gt;

&lt;p&gt;Keep feature branches short-lived. Branches that live for weeks diverge significantly from main and produce massive conflicts when merged. Aim for branches that live 1-3 days and get merged back through a pull request.&lt;/p&gt;

&lt;p&gt;Communicate about shared files. When you know you are making significant changes to a file that others also work in, mention it to the team. Simple coordination prevents most serious conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Branch Strategies
&lt;/h2&gt;

&lt;p&gt;GitFlow uses long-lived branches for different purposes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A main branch contains only production-ready code. &lt;/li&gt;
&lt;li&gt;A develop branch is the integration point where features are merged before going to production. &lt;/li&gt;
&lt;li&gt;Feature branches come off develop and merge back into develop. &lt;/li&gt;
&lt;li&gt;Release branches prepare for a production deployment. &lt;/li&gt;
&lt;li&gt;Hotfix branches patch production issues directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This maps cleanly to CI/CD: develop branch triggers deployment to the dev environment, release branch triggers staging deployment, main branch triggers production deployment with approval.&lt;/p&gt;

&lt;p&gt;Trunk-Based Development keeps almost everything in one branch — main (or trunk). Feature branches exist but are short-lived, typically 1-3 days maximum. Feature flags control what users see rather than long-lived branches. Every merge to main can safely deploy to production. This is the approach modern engineering teams use when they want to deploy multiple times per day.&lt;/p&gt;

&lt;p&gt;TechStack Blog uses simplified trunk-based: one main branch, everything deploys automatically. Works perfectly for a solo developer project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipeline Security
&lt;/h2&gt;

&lt;p&gt;Never put actual values for secrets in YAML files. A YAML file in a public GitHub repository is visible to everyone. Any secret in that file is compromised the moment you commit it. Use &lt;code&gt;${{ secrets.NAME }}&lt;/code&gt; and&lt;br&gt;
store the actual value in GitHub Secrets or Azure DevOps variable groups.&lt;/p&gt;

&lt;p&gt;Pin your action versions. Using &lt;code&gt;actions/checkout@main&lt;/code&gt; means your pipeline uses whatever version is current at the time it runs. That version can change without warning and silently break your pipeline or introduce malicious code. Use &lt;code&gt;actions/checkout@v4&lt;/code&gt; for a specific&lt;br&gt;
stable version.&lt;/p&gt;

&lt;p&gt;Give your service principal the minimum permissions it needs to deploy. If the pipeline only needs to deploy to one App Service, its Azure role should only allow that — not manage billing, not delete resources, not&lt;br&gt;
create new services.&lt;/p&gt;

&lt;p&gt;Use environment protection rules for production. In GitHub Settings, create a Production environment and require one or more specific people to approve before any deployment can proceed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons From Production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Set up CI/CD before writing business logic. Retrofitting a pipeline into an existing project is significantly harder than building it alongside the first commit. The TechStack Blog pipeline was configured on day one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep pipelines fast. If building and deploying takes more than 10 minutes, developers stop waiting for results and start pushing anyway. Cache dependencies, run tests in parallel, and skip unnecessary steps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test your rollback before you need it. A rollback plan you have never practiced is not a plan. Every team should do a rollback drill in staging at least once per quarter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never let a broken pipeline stay broken. A broken pipeline is ignored. An ignored pipeline means changes go unverified. Fix pipeline failures immediately — treat them with the same urgency as a production outage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;CI/CD automates the path from code to running software. YAML defines the pipeline steps in a readable, version- controlled format. GitHub Actions brings CI/CD directly into your GitHub workflow with zero additional tooling. Azure DevOps adds enterprise approval gates and environment management for organizations that need them. Merge conflicts are a normal part of collaborative development — understand the markers, resolve deliberately, prevent through good habits. Together these practices make deployment reliable, repeatable, and safe.&lt;/p&gt;

&lt;p&gt;The TechStack Blog goes from git push to live in 4 minutes automatically, every time, with no manual steps. That is what CI/CD is for.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://www.techstackblog.com/post.html?slug=cicd-yaml-azure-devops-deep-dive" rel="noopener noreferrer"&gt;https://www.techstackblog.com/post.html?slug=cicd-yaml-azure-devops-deep-dive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow for weekly posts on Azure, C#, DevOps, and cloud engineering.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
      <category>github</category>
      <category>cicd</category>
    </item>
    <item>
      <title>C# Lambda Expressions and LINQ: The Two Features That Changed How I Write Code</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Mon, 15 Jun 2026 14:48:23 +0000</pubDate>
      <link>https://dev.to/manoharij/c-lambda-expressions-and-linq-the-two-features-thatchanged-how-i-write-code-3amc</link>
      <guid>https://dev.to/manoharij/c-lambda-expressions-and-linq-the-two-features-thatchanged-how-i-write-code-3amc</guid>
      <description>&lt;p&gt;When I first started writing C# I used foreach loops for everything. Filter a list — foreach loop. Find one item — foreach loop. Transform every element — foreach loop. The code worked but it was verbose, repetitive, and hard to read at a glance.&lt;/p&gt;

&lt;p&gt;Lambda expressions and LINQ changed everything. The same operations became one line. The intent became immediately readable. And when I started using Entity Framework Core to query Azure SQL in the TechStack Blog API, LINQ became the way I wrote every single database query without touching raw SQL.&lt;/p&gt;

&lt;p&gt;These are not beginner concepts you learn once and forget. They are the foundation of every modern C# codebase. This post covers both deeply — with analogies, real code, and the mistakes that only become clear in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1 — Lambda Expressions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Analogy
&lt;/h3&gt;

&lt;p&gt;Think of a regular method like a full recipe card with a name, an ingredients list, and step-by-step instructions. You write it once, give it a name, and call it by name whenever you need it.&lt;/p&gt;

&lt;p&gt;A lambda expression is like a sticky note with a quick instruction written on it — no name, used once right where you need it, then gone. It is a method without a name, written inline exactly where it is used.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a Lambda Expression Is
&lt;/h3&gt;

&lt;p&gt;A lambda expression has two sides separated by the =&amp;gt; arrow operator (pronounced "goes to"):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Left side: the input parameter&lt;/li&gt;
&lt;li&gt;Right side: the expression that returns a value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;post =&amp;gt; post.IsPublished&lt;/code&gt; reads as: &lt;br&gt;
"given a post, return whether IsPublished is true"&lt;/p&gt;

&lt;p&gt;The equivalent named method would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsPublished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPublished&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda is not better or worse — it is shorter and used inline rather than declared separately and called by name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda Syntax Types
&lt;/h3&gt;

&lt;p&gt;Expression lambda for a single expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Statement lambda for multiple lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doubled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;doubled&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No parameters use empty parentheses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple parameters use parentheses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Func and Action
&lt;/h3&gt;

&lt;p&gt;Lambdas can be stored in variables using two built-in delegate types.&lt;/p&gt;

&lt;p&gt;Func has a return value. Func takes the input types first and the return type last:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;doubler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Action has no return value (void):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;printer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once stored in a variable, you call them like regular&lt;br&gt;
methods: &lt;code&gt;doubler(5) returns 10, printer("Hello") prints Hello&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Closures — Captured Variables
&lt;/h3&gt;

&lt;p&gt;Lambdas can capture variables from the surrounding scope. This is called a closure and it is both powerful and a source of subtle bugs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;techFilter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Azure"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;azurePosts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;techFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda captures techFilter from the outer scope. It remembers the variable — not a copy of its value at the time of capture, but the variable itself. If techFilter changes before the lambda executes, the lambda sees the new value.&lt;/p&gt;

&lt;p&gt;The classic bug is closures in loops. When you create a lambda inside a loop and use the loop variable, all lambdas end up referencing the same variable — which has its final value after the loop ends. Fix it by capturing a copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;captured&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// capture a copy of i right now&lt;/span&gt;
    &lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;captured&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without the int captured = i line, all three lambdas print the same final value of i. With it, each captures its own copy and prints 0, 1, 2 as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2 — LINQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Analogy
&lt;/h3&gt;

&lt;p&gt;LINQ is SQL for your C# collections. Just as SQL lets you query a database with SELECT, WHERE, ORDER BY and GROUP BY — LINQ lets you query any list, array, or collection in C# using the same concepts but in C# syntax.&lt;/p&gt;

&lt;p&gt;The magic is that LINQ works on everything — in-memory lists, Entity Framework database queries, XML documents, JSON collections. The same syntax, completely different data sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two Syntax Styles
&lt;/h3&gt;

&lt;p&gt;Method syntax uses dot chaining and is the most common in modern C#:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPublished&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderByDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Query syntax looks like SQL and reads naturally for developers coming from a database background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
     &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPublished&lt;/span&gt;
     &lt;span class="k"&gt;orderby&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="k"&gt;descending&lt;/span&gt;
     &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both produce identical results. Method syntax is more flexible and more commonly written in production code. Query syntax can be easier to read or complex joins.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Essential LINQ Methods
&lt;/h3&gt;

&lt;p&gt;Where filters elements — keeps only those where the predicate returns true:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPublished&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Azure"&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select transforms each element — projects to a new shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Excerpt&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OrderBy and OrderByDescending sort the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderByDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FirstOrDefault finds one element safely — returns null if nothing matches rather than throwing an exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"my-post"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always use FirstOrDefault over First in production. Production data is never as clean as you expect and First throws when nothing matches.&lt;/p&gt;

&lt;p&gt;Any and All return booleans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Azure"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="n"&gt;least&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;All&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPublished&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;every&lt;/span&gt; &lt;span class="n"&gt;single&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Count with a predicate counts matching elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Azure"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take and Skip enable pagination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GroupBy groups elements by a key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GroupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deferred Execution — The Most Important Concept
&lt;/h3&gt;

&lt;p&gt;This is the source of most LINQ bugs and the concept that trips up even experienced developers.&lt;/p&gt;

&lt;p&gt;LINQ queries do not execute when you define them. They execute when you iterate the results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Azure"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Nothing has happened yet. No filtering. No looping.&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// NOW the query executes. The filtering happens here.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical implication is that data can change between when you define a query and when you execute it. If you add a new Azure post after defining the query but before calling ToList, the new post appears in the results.&lt;/p&gt;

&lt;p&gt;With Entity Framework Core, deferred execution means no SQL is generated until you call ToListAsync, FirstOrDefaultAsync, CountAsync, or any other materializing method. This is why every EF Core query ends with one of these calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Most Important Performance Rule
&lt;/h3&gt;

&lt;p&gt;Never call ToList before Where.&lt;/p&gt;

&lt;p&gt;This loads your entire database table into memory as a C# list, then filters in C# instead of in SQL. For a table with a million rows this is catastrophic.&lt;/p&gt;

&lt;p&gt;Always filter first, materialize last:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Wrong&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Azure"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Azure"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The right version generates a SQL WHERE clause and only returns matching rows from the database. The wrong version loads every row then throws most of them away in memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  LINQ with Entity Framework Core
&lt;/h3&gt;

&lt;p&gt;This is where LINQ becomes truly powerful. Every LINQ query on a DbSet translates to SQL that runs against your database. You never write raw SQL for standard operations.&lt;/p&gt;

&lt;p&gt;The query that powers the TechStack Blog API homepage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Posts&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsPublished&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderByDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PostDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Slug&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Tech&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EF Core translates this to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tech&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Posts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;IsPublished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AsNoTracking tells EF Core this is read-only — it skips change tracking and significantly improves performance for queries that will not update data.&lt;/p&gt;

&lt;p&gt;Selecting into a DTO inside the query means only the columns you need come from the database. Without the Select, EF Core fetches every column even if you only use two of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  GroupBy in Practice
&lt;/h3&gt;

&lt;p&gt;GroupBy is the most underused LINQ method. It replaces entire blocks of code that build dictionaries manually with a clean readable pipeline.&lt;/p&gt;

&lt;p&gt;Before GroupBy (old style):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;techCounts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;techCounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ContainsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;techCounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;techCounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt;&lt;span class="p"&gt;]++;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After GroupBy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;techCounts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GroupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tech&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToDictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both produce the same dictionary. The GroupBy version is four lines shorter and immediately readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  SelectMany — Flattening Nested Collections
&lt;/h3&gt;

&lt;p&gt;SelectMany is LINQ's way of flattening a collection of collections into one flat collection.&lt;/p&gt;

&lt;p&gt;If each post has a list of tags, SelectMany gives you all tags from all posts as one flat list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;allTags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SelectMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Distinct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without SelectMany you would need a nested foreach loop to collect all tags from all posts. SelectMany does it in one step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons From Production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use FirstOrDefault not First everywhere. First throws an exception when nothing matches. In production data is messy and things that should always exist sometimes do not.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put ToList or ToListAsync at the very end. Deferred execution means the query runs where you materialize the results. Moving ToList earlier changes where the work happens and usually for the worse.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use AsNoTracking for every read-only query. EF Core tracks every object it loads by default so it can detect changes. For queries where you will never update the data this is wasted overhead. AsNoTracking is a free performance improvement.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn GroupBy deeply. Once you see how it replaces manual dictionary building the pattern appears everywhere and you will use it constantly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Understand the closure-in-loop bug before using lambdas in loops. It does not come up every day but when it does and you do not know about it the debugging is genuinely confusing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Lambda expressions give you unnamed inline functions that make code shorter and more expressive. LINQ gives you a consistent query language that works on any collection — lists, arrays, databases, XML. Together&lt;br&gt;
they are the two features that separate readable modern C# from verbose loop-heavy code.&lt;/p&gt;

&lt;p&gt;Every endpoint in the TechStack Blog API uses both. The database queries are LINQ. The data transformations are lambdas with Select. The filtering is Where with lambda predicates. Master these two concepts and every other C# pattern becomes easier to understand and implement.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://calm-island-0a7b4b30f.7.azurestaticapps.net/post.html?slug=csharp-lambda-linq-deep-dive" rel="noopener noreferrer"&gt;https://calm-island-0a7b4b30f.7.azurestaticapps.net/post.html?slug=csharp-lambda-linq-deep-dive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow for weekly posts on C#, Azure integration, and cloud engineering.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Azure Application Insights: Monitoring, KQL Queries and Observability in Production</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Sat, 13 Jun 2026 15:19:42 +0000</pubDate>
      <link>https://dev.to/manoharij/azure-application-insights-monitoring-kql-queries-and-observability-in-production-2nac</link>
      <guid>https://dev.to/manoharij/azure-application-insights-monitoring-kql-queries-and-observability-in-production-2nac</guid>
      <description>&lt;p&gt;At 2am on a Tuesday, an IP address change in Microsoft infrastructure silently broke our entire integration pipeline at Blue Yonder. Messages stopped flowing between Salesforce and ServiceNow. No errors surfaced in the application logs. The systems reported themselves as healthy. But nothing was moving.&lt;/p&gt;

&lt;p&gt;It was Azure Application Insights that found it.&lt;/p&gt;

&lt;p&gt;The Application Map showed 100% failure rate on the Service Bus node. I clicked the connection line. Saw connection refused errors on the dependency calls. Traced the root cause to the IP change within minutes.&lt;br&gt;
I wrote a Logic App to inspect the dead-letter queue and replay every failed message. Zero data loss. Zero SLA breach.&lt;/p&gt;

&lt;p&gt;That incident shaped how I think about observability forever. App Insights is not a monitoring tool you add at the end. It is the foundation you build everything on from day one.&lt;/p&gt;

&lt;p&gt;This post covers everything I learned using App Insights in production - the queries, the alerts, the patterns, and the lessons that only come from real incidents.&lt;/p&gt;
&lt;h2&gt;
  
  
  What App Insights Collects Automatically
&lt;/h2&gt;

&lt;p&gt;The first thing that surprises most engineers is how much App Insights collects with zero configuration. Add one NuGet package and one line in Program.cs and you immediately get:&lt;/p&gt;

&lt;p&gt;Every HTTP request your API receives - the URL, method, response code, duration, and whether it succeeded.&lt;/p&gt;

&lt;p&gt;Every dependency call your app makes outbound - SQL queries, HTTP calls to external APIs, Service Bus operations, Blob Storage reads. Each one tracked with duration and success status.&lt;/p&gt;

&lt;p&gt;Every unhandled exception with the full stack trace, the exact line of code that failed, and all exception properties including inner exceptions.&lt;/p&gt;

&lt;p&gt;Every log message you write with ILogger - Information, Warning, Error levels all captured with custom properties.&lt;/p&gt;

&lt;p&gt;Performance counters including CPU usage, memory consumption, and request queue length collected automatically on App Service.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting It Up in C# ASP.NET Core
&lt;/h2&gt;

&lt;p&gt;Three steps and you are done.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the NuGet package:&lt;br&gt;
Microsoft.ApplicationInsights.AspNetCore&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add one line in Program.cs:&lt;br&gt;
builder.Services.AddApplicationInsightsTelemetry(&lt;br&gt;
builder.Configuration["ApplicationInsights:ConnectionString"]&lt;br&gt;
);&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the connection string to appsettings.json or&lt;br&gt;
Azure App Service Configuration (never hardcode it).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. Every ILogger call in your code now goes to App Insights automatically. No code changes needed in your controllers or services.&lt;/p&gt;

&lt;p&gt;For custom events and metrics - tracking business-level events beyond technical telemetry - inject TelemetryClient and call TrackEvent() with a name and custom properties. I used this at Blue Yonder to track every integration completion with the system name and record count as&lt;br&gt;
properties, so I could query processing volumes by system over time.&lt;/p&gt;
&lt;h2&gt;
  
  
  KQL - The Query Language That Changes Everything
&lt;/h2&gt;

&lt;p&gt;KQL (Kusto Query Language) is what makes App Insights powerful rather than just a log viewer. It reads left to right with pipe operators - each step filters or transforms the result of the previous step.&lt;/p&gt;

&lt;p&gt;The basic structure is always the same:&lt;br&gt;
Start with a table name, then pipe through operators like where, project, summarize, order by, and extend.&lt;/p&gt;

&lt;p&gt;Once you understand five queries you can write almost any investigation query you need. Here are the five I used most in production: &lt;/p&gt;

&lt;p&gt;All failed requests in the last hour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requests
| where timestamp &amp;gt; ago(1h)
| where success == false
| project timestamp, name, url, resultCode, duration
| order by timestamp desc

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was my first query every morning and after every deployment. If it returned rows, I had work to do.&lt;/p&gt;

&lt;p&gt;Slowest API endpoints today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requests
| where timestamp &amp;gt; ago(24h)
| summarize AvgDuration = avg(duration), Count = count() by name
| order by AvgDuration desc
| take 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This identified performance regressions immediately after deployments before customers reported them.&lt;/p&gt;

&lt;p&gt;All exceptions with full detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exceptions
| where timestamp &amp;gt; ago(4h)
| project timestamp, type, outerMessage, innermostMessage, method
| order by timestamp desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The innermostMessage field is the one you want - it has the root cause, not the wrapper exception.&lt;/p&gt;

&lt;p&gt;Dependency failures - what external calls failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies
| where timestamp &amp;gt; ago(1h)
| where success == false
| project timestamp, type, target, name, duration, resultCode
| order by timestamp desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was how I found the Microsoft IP change. Service Bus dependencies all showing connection refused, all starting at the same timestamp.&lt;/p&gt;

&lt;p&gt;Error rate by hour - spot patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requests
| where timestamp &amp;gt; ago(24h)
| summarize Total=count(), Failed=countif(success==false) by bin(timestamp,1h)
| extend ErrorRate = round(Failed*100.0/Total, 2)
| order by timestamp asc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This chart pattern shows you whether failures are random noise or a systematic problem getting worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Queries I Wrote at Blue Yonder
&lt;/h2&gt;

&lt;p&gt;Beyond the standard queries, I built several patterns specific to integration monitoring that I have not seen documented elsewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-system correlation&lt;/strong&gt; - following one record through every system it touched. Every request in our pipeline carried a CorrelationId custom property. With this query I could trace a single Salesforce case through Logic App orchestration, Function App transformation, Service Bus&lt;br&gt;
messaging, and the final ServiceNow API call - seeing exact timestamps and durations at each step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;union requests, dependencies, traces, exceptions
| where timestamp &amp;gt; ago(24h)
| where tostring(customDimensions["CorrelationId"]) == "your-id"
| project timestamp, itemType, name, message, duration, success
| order by timestamp asc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Token refresh monitoring&lt;/strong&gt; - tracking the 3-month Salesforce and 6-month ServiceNow credential refresh cycles that were a significant operational risk before I centralized them in Key Vault:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customEvents
| where name == "TokenRefreshed"
| extend System = tostring(customDimensions["System"])
| summarize LastRefresh=max(timestamp), SuccessCount=countif(tobool(customDimensions["Success"])==true) by System

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integration pipeline health - the Monday morning query that showed overnight processing status for every integration flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requests
| where timestamp &amp;gt; ago(12h)
| where name contains "Integration"
| summarize Success=countif(success==true), Failed=countif(success==false), AvgDuration=avg(duration) by name
| extend Status = iff(Failed &amp;gt; 0, "DEGRADED", "HEALTHY")
| order by Status asc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Smart Alerts - Stop Watching Dashboards
&lt;/h2&gt;

&lt;p&gt;The real power of App Insights is alerts that find problems for you.&lt;/p&gt;

&lt;p&gt;Metric alerts fire when a number crosses a threshold - failed requests greater than 5 in 5 minutes, response time average greater than 2 seconds, exception count greater than 10 per hour.&lt;/p&gt;

&lt;p&gt;Log alerts run a KQL query on a schedule and fire if the results meet a condition. This is how I monitored the dead-letter queue - a query that ran every 5 minutes and fired immediately if any messages appeared in the DLQ. In production, a non-empty DLQ is always a signal that&lt;br&gt;
something needs investigation.&lt;/p&gt;

&lt;p&gt;Smart detection requires no configuration. App Insights learns your baseline automatically and alerts on anomalies - unusual failure rate spikes, abnormal response time degradation, memory leak patterns.&lt;br&gt;
It caught two issues at Blue Yonder that I would not have noticed from metrics alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Metrics During Deployments
&lt;/h2&gt;

&lt;p&gt;Live Metrics shows you what is happening with less than one second latency - incoming requests per second, exception rate, dependency call rate, CPU and memory of every running instance.&lt;/p&gt;

&lt;p&gt;I had Live Metrics open on a second monitor during every production deployment. If the exception rate spiked within 30 seconds of a deploy I knew immediately to roll back. If it stayed flat for 2 minutes the deployment was clean.&lt;/p&gt;

&lt;p&gt;This practice caught one bad deployment at Blue Yonder that would have caused a production incident if we had waited for customer reports. The rollback took 90 seconds. The alternative would have been hours of incident response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application Map
&lt;/h2&gt;

&lt;p&gt;The Application Map is the fastest way to understand what broke and where. It shows every component of your system as a node - your API, the SQL database, Service Bus, external APIs - with connection lines showing call volume and failure rate between them.&lt;/p&gt;

&lt;p&gt;When the Microsoft IP change broke our pipeline, the Service Bus node on the Application Map turned red with a 100% failure rate. I clicked the connection line between our API and Service Bus. The details pane&lt;br&gt;
showed connection refused with the target IP address. That one click saved 30 minutes of log digging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Tracing
&lt;/h2&gt;

&lt;p&gt;Every request in App Insights gets a unique Operation ID that flows automatically through every system it touches. A single user request that goes through APIM, Logic App, Function App, Service Bus, and SQL - all with the same Operation ID.&lt;/p&gt;

&lt;p&gt;In Transaction Search, paste the Operation ID and see every step in chronological order with exact timestamps and durations. The full story of one request across your entire distributed system in one view.&lt;/p&gt;

&lt;p&gt;The practical implication for code: add a CorrelationId to your custom log entries and custom events. Then you can find every log entry related to one business transaction even when it crosses system boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting App Insights Across the Azure Stack
&lt;/h2&gt;

&lt;p&gt;App Insights works with every Azure service. Logic Apps through diagnostic settings send all run history to the same Log Analytics workspace you query with KQL. Function Apps with AddApplicationInsightsTelemetry()&lt;br&gt;
track every function execution automatically. APIM connected to App Insights logs every API call with the caller's subscription key. Service Bus diagnostic settings expose message counts and DLQ depth as metrics.&lt;/p&gt;

&lt;p&gt;The goal is one workspace where you can query across all these data sources simultaneously. When an incident spans multiple systems - which in integration work they always do - you want one place to look, not five dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lessons From Production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Set up App Insights before you write business logic. Retrofitting observability into an existing system is ten times harder than building it in from the start.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use structured logging with named parameters. log.LogInformation("Processing {OrderId}", order.Id) is infinitely more queryable than string concatenation. The named parameter becomes a filterable field in KQL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add CorrelationId to everything. Every log entry, every custom event, every Service Bus message property. Cross-system tracing without it is guesswork.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn five queries well rather than memorizing dozens. The queries in this post covered 90% of every real incident investigation I ran at Blue Yonder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set DLQ alerts before going live. Silent message loss in a Service Bus integration is the hardest production bug to diagnose after the fact. The alert costs 5 minutes to set up. The incident it prevents costs days.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable sampling in production. A busy integration platform generates GBs of telemetry per day at full collection. Adaptive sampling preserves all errors and exceptions while reducing successful request volume to stay within cost limits.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  App Insights in the TechStack Blog
&lt;/h2&gt;

&lt;p&gt;The C# ASP.NET Core API powering this blog has App Insights enabled. Every visit to techstackblog.com that triggers an API call is tracked - the request duration, the SQL query to Azure SQL, any exceptions. The connection string lives in Azure App Service Configuration, not in the code or GitHub.&lt;/p&gt;

&lt;p&gt;If you are reading this post and wondering whether it is being monitored - it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Azure Application Insights gives you complete visibility into every layer of your Azure stack. Automatic telemetry collection means zero-config coverage from day one. KQL gives you the ability to answer any question about your system's behavior. Smart alerts find problems before your customers do. Application Map shows you what broke. Distributed tracing shows you why.&lt;/p&gt;

&lt;p&gt;Build it in from day one. Set your alerts. Learn your five queries. Then stop firefighting and start preventing.&lt;/p&gt;




&lt;p&gt;Originally published at TechStack Blog:&lt;br&gt;
&lt;a href="https://calm-island-0a7b4b30f.7.azurestaticapps.net/post.html?slug=azure-app-insights-deep-dive" rel="noopener noreferrer"&gt;https://calm-island-0a7b4b30f.7.azurestaticapps.net/post.html?slug=azure-app-insights-deep-dive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow for weekly posts on Azure integration, C#, observability, and cloud engineering.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>cloudcomputing</category>
    </item>
    <item>
      <title>Azure Function Apps in C#: Triggers, Bindings, Durable Functions and Everything I Learned in Production</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Thu, 11 Jun 2026 12:24:40 +0000</pubDate>
      <link>https://dev.to/manoharij/azure-function-apps-in-c-triggers-bindings-durable-functions-and-everything-i-learned-in-2om0</link>
      <guid>https://dev.to/manoharij/azure-function-apps-in-c-triggers-bindings-durable-functions-and-everything-i-learned-in-2om0</guid>
      <description>&lt;p&gt;Azure Function Apps are a serverless compute service that runs small focused pieces of C# code in response to events. You write a function, deploy it to Azure, and it runs only when triggered — paying only for the milliseconds it actually executes, not for a server sitting idle 24 hours a day.&lt;br&gt;
I used Function Apps at Blue Yonder as the transformation and processing layer in our integration pipelines. Logic Apps handled orchestration, Service Bus handled messaging, and Function Apps handled the complex C# logic that was too sophisticated for Logic App expressions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Function App vs Logic App vs API Management&lt;/strong&gt;&lt;br&gt;
This is the question every Azure engineer faces. Here is the honest answer:&lt;br&gt;
Use &lt;em&gt;Function Apps&lt;/em&gt; when you need complex C# business logic, custom data transformation algorithms, high performance compute, or reusable code components across multiple pipelines.&lt;br&gt;
Use &lt;em&gt;Logic Apps&lt;/em&gt; when you are connecting existing services visually, the workflow has clear sequential steps, or you need one of the 500+ built-in connectors.&lt;br&gt;
Use &lt;em&gt;API Management&lt;/em&gt; when exposing APIs to external consumers, applying rate limiting and authentication, or managing API versioning and documentation.&lt;br&gt;
The real answer for enterprise integrations is use all three together. APIM as the front door, Logic App as the orchestrator, Function App for complex transformations, Service Bus for reliable messaging between them. This is exactly the pattern I built at Blue Yonder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Six Trigger Types&lt;/strong&gt;&lt;br&gt;
A trigger is what causes your function to run. Every function must have exactly one trigger.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;HTTP Trigger runs when an HTTP request arrives. Use for REST APIs, webhooks, and callbacks from external systems.&lt;/li&gt;
&lt;li&gt;Timer Trigger runs on a schedule using a CRON expression. Use for nightly jobs, hourly reports, and cleanup tasks. The expression "0 0 2 * * *" runs at 2am every day.&lt;/li&gt;
&lt;li&gt;Service Bus Trigger runs when a message arrives on a queue or topic subscription. This was my most used trigger at Blue Yonder. It automatically handles retries and dead lettering — your function completing successfully means the message is completed, throwing an exception means it gets retried.&lt;/li&gt;
&lt;li&gt;Blob Storage Trigger runs when a file is uploaded to Azure Blob Storage. Use for image processing, file transformation, and document parsing.&lt;/li&gt;
&lt;li&gt;Queue Storage Trigger runs when a message arrives in an Azure Storage Queue. Good for simple high-volume task processing at the lowest cost.&lt;/li&gt;
&lt;li&gt;Event Grid Trigger runs when an Azure event occurs — like a new user created in Azure AD or a resource deployed in your subscription. Use for reacting to platform-level events.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Input and Output Bindings&lt;/strong&gt;&lt;br&gt;
Bindings are the most underused feature of Function Apps. They connect your function to other Azure services without writing any connection boilerplate code.&lt;br&gt;
An input binding reads data into your function automatically. An output binding writes data out of your function automatically. You declare what you need in the function signature and Azure handles the connections.&lt;br&gt;
Instead of writing 20 lines of SQL connection code to read a record, you add one attribute to your parameter and Azure injects the data directly. Instead of writing Service Bus SDK code to send a message, you add an output binding and just call AddAsync with your object.&lt;br&gt;
This eliminates entire classes of connection bugs and makes functions dramatically easier to read and test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Durable Functions — Long Running Workflows&lt;/strong&gt;&lt;br&gt;
Regular functions run for a maximum of 10 minutes on the Consumption plan. Durable Functions solve this with a pattern that can run for hours, days, or even months.&lt;br&gt;
There are three function types. The &lt;strong&gt;&lt;em&gt;Orchestrator&lt;/em&gt;&lt;/strong&gt; coordinates the workflow — calling activity functions in sequence or parallel, handling retries and timeouts, with state automatically checkpointed so it survives restarts. The &lt;strong&gt;&lt;em&gt;Activity&lt;/em&gt;&lt;/strong&gt; does the actual work — calling external APIs, querying databases, sending messages. The &lt;em&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;/em&gt; starts the workflow, usually via an HTTP trigger, and returns an instance ID for status checking.&lt;br&gt;
I used Durable Functions at Blue Yonder for integration flows that needed to wait for external system responses — start the flow, call ServiceNow, wait up to 24 hours for a callback, then continue processing. Without Durable Functions this required complex custom state management. With them it was straightforward orchestration code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connecting to Azure Key Vault&lt;/strong&gt;&lt;br&gt;
Never hardcode connection strings in your function code. Use Key Vault references in your application settings instead.&lt;br&gt;
In the Azure Portal under your Function App Configuration, add an application setting with a Key Vault reference as the value. Azure resolves the reference automatically at runtime — your code just reads the setting name as a normal environment variable. Enable Managed Identity on your Function App and grant it the Key Vault Secrets User role. No passwords anywhere in code or config files, ever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hosting Plans — Which to Choose&lt;/strong&gt;&lt;br&gt;
The &lt;em&gt;&lt;strong&gt;Consumption plan&lt;/strong&gt;&lt;/em&gt; charges per execution at roughly $0.20 per million calls. It auto-scales automatically but has a 10 minute timeout and 1-3 second cold starts on first call. Perfect for unpredictable traffic and low volume workloads.&lt;br&gt;
The &lt;em&gt;&lt;strong&gt;Premium plan&lt;/strong&gt;&lt;/em&gt; costs around $140 per month minimum but eliminates cold starts with pre-warmed instances, has no timeout limit, and supports VNet integration. Use this for production customer-facing functions where latency matters.&lt;br&gt;
The &lt;em&gt;&lt;strong&gt;Dedicated plan&lt;/strong&gt;&lt;/em&gt; runs on an existing App Service plan. Use this when you already have App Service infrastructure and want consistent predictable load without the serverless cold start behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Lessons From Production&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always use Managed Identity and Key Vault — never hardcode connection strings anywhere.&lt;/li&gt;
&lt;li&gt;Service Bus trigger is more reliable than HTTP for processing integration messages — it handles retries, dead lettering, and message locking automatically.&lt;/li&gt;
&lt;li&gt;Use Durable Functions for any workflow that might run longer than 10 minutes or needs to wait for external callbacks.&lt;/li&gt;
&lt;li&gt;Premium plan eliminates cold starts for customer-facing functions — the Consumption plan cold start is acceptable for background processing but not for real-time user requests.&lt;/li&gt;
&lt;li&gt;Keep functions small and focused — one function should do one job. If your function is doing three things, split it into three functions.&lt;/li&gt;
&lt;li&gt;Always add local.settings.json to your .gitignore file — it contains your local connection strings and must never be committed to GitHub.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Complete Azure Integration Stack&lt;/strong&gt;&lt;br&gt;
This is the architecture that handles enterprise-scale integrations reliably:&lt;br&gt;
External requests enter through &lt;em&gt;Azure API Management&lt;/em&gt; which handles authentication, rate limiting, and routing. &lt;br&gt;
&lt;em&gt;Logic Apps&lt;/em&gt; orchestrate the business workflow using built-in connectors. &lt;em&gt;Azure Service Bus&lt;/em&gt; provides reliable messaging between components with dead letter queues for failures.&lt;br&gt;
&lt;em&gt;Function Apps&lt;/em&gt; handle complex C# transformation logic that Logic Apps cannot express. &lt;br&gt;
&lt;em&gt;Azure SQL Database&lt;/em&gt; provides persistent storage. &lt;br&gt;
&lt;em&gt;Application Insights&lt;/em&gt; monitors every step of the entire stack automatically.&lt;br&gt;
Each layer has one responsibility. No layer does what another layer is designed for. This separation is what makes the architecture maintainable and debuggable at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br&gt;
Azure Function Apps are the C# powerhouse of the Azure integration stack. Serverless execution means zero infrastructure management. Six trigger types connect to every Azure service. Bindings eliminate boilerplate connection code. Durable Functions handle complex long-running workflows.&lt;br&gt;
Combined with Logic Apps for orchestration, Service Bus for messaging, and APIM for the front door — Function Apps complete the enterprise integration toolkit on Azure.&lt;/p&gt;

&lt;p&gt;Originally published at TechStack Blog — &lt;a href="https://calm-island-0a7b4b30f.7.azurestaticapps.net/post.html?slug=azure-function-apps-deep-dive" rel="noopener noreferrer"&gt;https://calm-island-0a7b4b30f.7.azurestaticapps.net/post.html?slug=azure-function-apps-deep-dive&lt;/a&gt;&lt;br&gt;
Follow for weekly posts on Azure integration, C#, and cloud engineering.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Azure API Management: Gateway, Policies, Authentication and Everything I Learned in Production</title>
      <dc:creator>Manohari Jayachandran</dc:creator>
      <pubDate>Wed, 10 Jun 2026 21:39:43 +0000</pubDate>
      <link>https://dev.to/manoharij/azure-api-management-gateway-policies-authentication-and-everything-i-learned-in-production-47g2</link>
      <guid>https://dev.to/manoharij/azure-api-management-gateway-policies-authentication-and-everything-i-learned-in-production-47g2</guid>
      <description>&lt;p&gt;Azure API Management (APIM) is a fully managed gateway that sits in front of all your backend APIs. Instead of exposing your raw APIs directly to consumers, every request goes through APIM first. It handles security, rate limiting, transformation, monitoring, and documentation — all in one place.&lt;br&gt;
I used APIM at Blue Yonder as the front door for all integrations on the SIAM platform. Every external system — Salesforce, ServiceNow, third-party vendors — called our APIs through APIM. It gave us one place to control, monitor, and secure everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Components&lt;/strong&gt;&lt;br&gt;
API Gateway receives all incoming API calls, applies policies before forwarding to the backend, and returns the response to the caller. This is the actual runtime component that handles every single request.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Developer Portal&lt;/strong&gt;&lt;/em&gt; is an auto-generated documentation website where developers can discover and test your APIs, subscribe for API keys, and get started without calling your team. It is fully customizable with your branding.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Management Plane&lt;/strong&gt;&lt;/em&gt; is the Azure Portal interface where you define APIs, products, policies, and users. You can import APIs directly from OpenAPI specs, WSDL files, Logic Apps, or Function Apps.&lt;br&gt;
Analytics automatically logs every request with response times, error rates, and usage broken down by consumer. Built-in dashboards show you exactly what is happening across all your APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;APIs, Products and Subscriptions&lt;/strong&gt;&lt;br&gt;
Understanding this three-level hierarchy is the key to using APIM correctly.&lt;br&gt;
An API is a group of related operations. For example an Orders API would contain&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /orders, GET /orders/{id}, POST /orders, and DELETE /orders/{id}.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Product is a bundle of one or more APIs with its own subscription keys and policies. A Partner Product might contain the Orders API and Inventory API with a limit of 5000 calls per day and require approval to subscribe.&lt;br&gt;
A Subscription is a key pair that grants access to a Product. Every caller gets their own subscription key. You can revoke individual keys without affecting any other consumer — critical for security incidents.&lt;br&gt;
At Blue Yonder we had three products:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal Product — no rate limit, no approval needed&lt;/li&gt;
&lt;li&gt;Partner Product — 5000 calls per day, requires approval&lt;/li&gt;
&lt;li&gt;Public Product — 100 calls per day, open signup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Policies — The Heart of APIM&lt;/strong&gt;&lt;br&gt;
Policies are rules that run on every request or response. Written in XML, they are applied at four levels — Global, Product, API, or Operation — giving you incredibly fine-grained control.&lt;br&gt;
Every request goes through four policy sections in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inbound — runs before the request reaches your backend&lt;/li&gt;
&lt;li&gt;Backend — controls how the backend is called&lt;/li&gt;
&lt;li&gt;Outbound — runs before the response reaches the caller&lt;/li&gt;
&lt;li&gt;On-Error — runs when any policy throws an exception&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the policies I used most in production:&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt;&lt;/em&gt; — limits a caller to a set number of calls per time window. Returns 429 Too Many Requests when exceeded.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Quota&lt;/strong&gt;&lt;/em&gt; — a hard cumulative limit over a longer period. Different from rate limiting — this counts total calls over the day rather than calls per minute.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;IP filtering&lt;/strong&gt;&lt;/em&gt; — only allow requests from specific IP address ranges. Essential for internal APIs that should never be called from the public internet.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Validate JWT&lt;/strong&gt;&lt;/em&gt; — validates an Azure AD JWT token on every request. This is the most important security policy for external-facing APIs. It checks the token is valid, not expired, and contains the required claims.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Set header&lt;/strong&gt;&lt;/em&gt; — adds or modifies a header on the backend request. We used this to pass internal API keys to backend services without ever exposing them to callers. The caller sends their subscription key. APIM validates it then adds the real internal key before forwarding.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Rewrite URL&lt;/strong&gt;&lt;/em&gt; — maps the external URL to a different internal URL. Callers hit /orders/{id} while the backend receives /v2/orders/{id}. This lets you version your backend without breaking existing callers.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Cache response&lt;/strong&gt;&lt;/em&gt; — caches GET responses for a configurable duration. We cached inventory lookups for 5 minutes which reduced backend load by 60 percent during peak hours.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Transform JSON to XML&lt;/strong&gt;&lt;/em&gt; — converts a JSON request body to XML before forwarding to legacy backends. Modern callers send JSON, APIM handles the transformation transparently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication Options&lt;/strong&gt;&lt;br&gt;
APIM supports four authentication methods. You choose based on who is calling and what level of security you need.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Subscription Key&lt;/strong&gt;&lt;/em&gt; is the simplest option. The caller passes a key in the Ocp-Apim-Subscription-Key header. Good for partner and internal system integrations where you control both ends.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;OAuth 2.0&lt;/strong&gt;&lt;/em&gt; with Azure AD is the most secure option for external APIs. The caller gets a JWT token from Azure AD first then passes it as a Bearer token. APIM validates it using the validate-jwt policy. The token contains the caller identity, roles, and permissions. Use this for all user-facing apps and B2B enterprise integrations.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Client Certificate (mTLS)&lt;/strong&gt;&lt;/em&gt; requires the caller to present a certificate instead of a password. APIM validates the certificate thumbprint. We used this at Blue Yonder for banking integrations where the highest security was required.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;Basic Authentication&lt;/strong&gt;&lt;/em&gt; passes username and password encoded as Base64. Only use this for legacy systems that support nothing else and always over HTTPS. Avoid for any new integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Named Values — Storing Secrets Safely&lt;/strong&gt;&lt;br&gt;
Named Values are APIM variables that store sensitive values like API keys and connection strings. You reference them in policies using double curly braces without ever hardcoding the actual value.&lt;br&gt;
When you create a Named Value you mark it as secret. The value is encrypted at rest and never visible in the portal after saving. Best practice is to link Named Values directly to Azure Key Vault so secrets rotate automatically without any policy changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versioning and Revisions&lt;/strong&gt;&lt;br&gt;
Versions handle breaking changes. You run v1 and v2 simultaneously at different URLs. Callers choose which version to use and you retire v1 only after all callers have migrated. No forced upgrades.&lt;br&gt;
Revisions handle non-breaking changes. You create Revision 2 as a work in progress that is not live yet. Make and test your changes safely. Promote to live when ready. Instantly roll back to Revision 1 if something goes wrong.&lt;br&gt;
This is how we deployed all APIM changes at Blue Yonder — zero downtime, zero caller impact, instant rollback capability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring and Tracing&lt;/strong&gt;&lt;br&gt;
Every APIM request automatically logs the timestamp, caller IP, subscription key used, operation called, response time, and HTTP status code.&lt;br&gt;
The most useful debugging tool is request tracing. Add the Ocp-Apim-Trace: true header to any request and the response includes a full trace of every policy step — exactly what data entered each policy, what came out, and how long it took. This saved me hours of debugging at Blue Yonder. You can see precisely which policy transformed what and where a request slowed down or failed.&lt;br&gt;
Connect APIM to Application Insights and every request appears there automatically. Set alerts on error rate thresholds or response time degradation to catch problems before your callers do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Full Enterprise Integration Pattern&lt;/strong&gt;&lt;br&gt;
This is the complete architecture I built at Blue Yonder combining APIM with Logic Apps, Service Bus, and Azure SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`External System
    |
    | HTTPS with JWT token
    v
Azure APIM
    Validates JWT token
    Applies rate limiting
    Adds correlation ID header
    Rewrites URL to internal format
    |
    v
Azure Logic App
    Orchestrates business workflow
    Calls ServiceNow and Salesforce APIs
    |
    v
Azure Service Bus
    Reliable async messaging
    Dead letter queue for failures
    |
    v
Azure App Service API
    Processes the message
    Updates the database
    |
    v
Azure SQL Database`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer has one job. APIM handles the front door. Logic Apps handles orchestration. Service Bus handles reliability. The API handles business logic. SQL handles persistence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Lessons From Production&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always use Named Values for secrets — never hardcode API keys or passwords directly in policy XML.&lt;/li&gt;
&lt;li&gt;Apply rate limiting at Product level not Global — different consumers have different needs. Partners get more calls than public users.&lt;/li&gt;
&lt;li&gt;Use Revisions for every change — instant rollback has saved production incidents more than once.&lt;/li&gt;
&lt;li&gt;Enable Application Insights from day one — you will need those logs the moment something goes wrong in production.&lt;/li&gt;
&lt;li&gt;Use the Trace header during development — it shows exactly what every policy does to every request. Invaluable for debugging complex policy chains.&lt;/li&gt;
&lt;li&gt;Set up the Developer Portal early — partners and consumers love self-service documentation. It reduces support requests dramatically.&lt;/li&gt;
&lt;li&gt;Use validate-jwt for all external APIs — subscription keys alone are not enough. Tokens give you identity, roles, and expiry.&lt;/li&gt;
&lt;li&gt;Add circuit breaker on every backend — protect your services from cascade failures when a downstream system goes down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;APIM Tiers&lt;/strong&gt;&lt;br&gt;
The Consumption tier charges per call with no monthly commitment — perfect for learning and low traffic APIs. The Developer tier costs around $50 per month with full features but no SLA — good for development and testing. Basic at around $150 per month is the entry point for production workloads. Standard at around $700 per month adds VNet support for enterprise use. Premium at around $2800 per month enables multi-region deployment and zone redundancy for global enterprise applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br&gt;
Azure API Management is the professional way to expose APIs in enterprise Azure architectures. One gateway to handle security, rate limiting, transformation, versioning, and monitoring for all your APIs.&lt;br&gt;
Combined with Azure AD for authentication, Key Vault for secrets, Logic Apps for orchestration, and Service Bus for messaging — APIM completes the enterprise integration stack on Azure. If you are building serious integrations on Azure, APIM is not optional. It is the foundation everything else builds on.&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://calm-island-0a7b4b30f.7.azurestaticapps.net/post.html?slug=azure-api-management-deep-dive" rel="noopener noreferrer"&gt;https://calm-island-0a7b4b30f.7.azurestaticapps.net/post.html?slug=azure-api-management-deep-dive&lt;/a&gt;&lt;br&gt;
If you found this useful, follow me for more posts on Azure integration, C#, and cloud engineering.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>cloudcomputing</category>
    </item>
  </channel>
</rss>
