<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Wiem Ksaier on Medium]]></title>
        <description><![CDATA[Stories by Wiem Ksaier on Medium]]></description>
        <link>https://medium.com/@wiemksaier?source=rss-a44b0eac5191------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*uLfHcoPtGlQdWjpwLsMteQ.jpeg</url>
            <title>Stories by Wiem Ksaier on Medium</title>
            <link>https://medium.com/@wiemksaier?source=rss-a44b0eac5191------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 17:41:01 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@wiemksaier/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Clean Architecture in ASP.NET Core: Understanding the Why Behind Every Layer]]></title>
            <link>https://wiemksaier.medium.com/clean-architecture-in-asp-net-core-understanding-the-why-behind-every-layer-4a7763198834?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/4a7763198834</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[aspnet]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[clean-architecture]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Thu, 29 Oct 2026 21:34:47 GMT</pubDate>
            <atom:updated>2026-10-29T21:34:47.311Z</atom:updated>
            <content:encoded><![CDATA[<p><em>From tightly coupled code to a maintainable application explained step by step.</em></p><p>Have you ever opened an ASP.NET Core solution and wondered why it contains multiple projects named <strong>Domain, Application, Infrastructure, and API</strong>?</p><p>Why not simply put everything inside controllers and services?</p><p>And why do developers introduce interfaces when they could directly use Entity Framework Core?</p><p>The answer lies in one important concept: <strong>separation of concerns and control over dependencies.</strong></p><p>Let’s understand Clean Architecture through a practical example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/772/1*B7LkQDyDqLN3rRSrNYkETA.jpeg" /></figure><h3>1. What Is Clean Architecture, Really?</h3><p>Clean Architecture, introduced by Robert C. Martin, is an architectural approach that organizes software around its <strong>business rules rather than its technical implementation details</strong>.</p><p>Imagine we’re developing an e-commerce application.</p><p>Our system needs to:</p><ul><li>Create and manage orders.</li><li>Calculate order totals.</li><li>Save orders in a database.</li><li>Send confirmation emails.</li></ul><p>Some of these are <strong>business responsibilities</strong> (creating and validating orders), while others are <strong>technical responsibilities</strong> (database access and email delivery).</p><p>The objective is simple:</p><p><strong>Business logic should remain independent of frameworks, databases, and external services.</strong></p><p>This way, changing SQL Server to PostgreSQL, for example, doesn’t require rewriting how orders are created or validated.</p><h3>2. The Problem: Tightly Coupled Code</h3><p>Consider this simplified ASP.NET Core controller:</p><pre>[HttpPost]<br>public async Task&lt;IActionResult&gt; CreateOrder(<br>    CreateOrderRequest request)<br>{<br>    var order = new Order<br>    {<br>        Total = request.Quantity * request.Price<br>    };<br>    _context.Orders.Add(order);<br>    await _context.SaveChangesAsync();<br>    return Ok(order);<br>}</pre><p>It works. So what’s the problem?</p><p>Our controller is handling HTTP communication, calculating the order total, and managing persistence.</p><p>As business requirements grow, controllers can become difficult to maintain and business logic becomes harder to reuse and test independently.</p><p>Clean Architecture helps us separate these responsibilities.</p><h3>3. Understanding the Four Layers</h3><p>A common ASP.NET Core Clean Architecture implementation consists of four layers.</p><h3>Domain — The heart of the application</h3><p>The Domain layer contains business entities and rules.</p><p>For example:</p><pre>public class Order<br>{<br>    public decimal Total { get; private set; }<br><br>    public Order(int quantity, decimal price)<br>    {<br>        if (quantity &lt;= 0 || price &lt;= 0)<br>            throw new ArgumentException(<br>                &quot;Invalid order values&quot;);<br>        Total = quantity * price;<br>    }<br>}</pre><p>Notice something important: <strong>there is no EF Core, SQL Server, or ASP.NET Core dependency here.</strong></p><p>The entity only knows its business rules.</p><p>In a real e-commerce system, the price would normally come from trusted product data rather than directly from the client’s request.</p><h3>Application — What the system can do</h3><p>The Application layer defines use cases such as:</p><ul><li>CreateOrder</li><li>CancelOrder</li><li>GetOrderById</li></ul><p>It coordinates business operations without knowing how infrastructure works.</p><pre>public class CreateOrderHandler<br>{<br>    private readonly IOrderRepository _repository;<br>    public CreateOrderHandler(<br>        IOrderRepository repository)<br>    {<br>        _repository = repository;<br>    }<br>    public async Task Handle(<br>        int quantity,<br>        decimal price)<br>    {<br>        var order = new Order(quantity, price);<br>        await _repository.AddAsync(order);<br>    }<br>}</pre><p>The repository contract is defined inside the Application layer:</p><pre>public interface IOrderRepository<br>{<br>    Task AddAsync(Order order);<br>}</pre><p>Why an interface? This brings us to the most important principle in Clean Architecture.</p><h3>Infrastructure — How technical operations work</h3><p>Infrastructure contains concrete implementations for database access, email services, external APIs, and similar concerns.</p><p>For example:</p><pre>public class OrderRepository : IOrderRepository<br>{<br>    private readonly AppDbContext _context;<br>    public OrderRepository(AppDbContext context)<br>    {<br>        _context = context;<br>    }<br>    public async Task AddAsync(Order order)<br>    {<br>        await _context.Orders.AddAsync(order);<br>        await _context.SaveChangesAsync();<br>    }<br>}</pre><p>Here, Entity Framework Core manages database persistence.</p><p>The important detail is that <strong>Infrastructure implements the interface defined by Application</strong>.</p><p>Application doesn’t depend on the concrete repository.</p><h3>Presentation — How users interact with the application</h3><p>The Presentation layer exposes the use cases through ASP.NET Core controllers or Minimal APIs.</p><pre>[HttpPost]<br>public async Task&lt;IActionResult&gt; CreateOrder(<br>    CreateOrderRequest request)<br>{<br>    await _handler.Handle(<br>        request.Quantity,<br>        request.Price);<br>   return Ok();<br>}</pre><p>The controller receives the request and delegates the operation to Application.</p><p>It doesn’t contain persistence logic or decide how the order is saved.</p><h3>4. The Golden Rule: Dependency Inversion</h3><p>The most important rule of Clean Architecture is:</p><p><strong>Dependencies should point inward, toward the business core.</strong></p><p>The relationships generally look like this:</p><pre>API ─────────────→ Application<br> │                       │<br> │                       ↓<br> │                    Domain<br> ↓<br>Infrastructure ───→ Application<br>       │<br>       └──────────→ Domain</pre><p>Why does Infrastructure depend on Application?</p><p>Because Application defines IOrderRepository, and Infrastructure implements it.</p><p>Without dependency inversion:</p><pre>Application → SqlOrderRepository</pre><p>With dependency inversion:</p><pre>Application → IOrderRepository<br>                    ↑<br>               OrderRepository<br>               (Infrastructure)</pre><p>The Application layer defines <strong>what it needs</strong>, while Infrastructure provides <strong>how it works</strong>.</p><p>This is the Dependency Inversion Principle (the D in SOLID).</p><h3>Dependency Inversion vs. Dependency Injection</h3><p>They sound similar, but they’re different.</p><ul><li><strong>Dependency Inversion</strong> is a design principle describing how dependencies should be organized.</li><li><strong>Dependency Injection</strong> is a technique for supplying dependencies to classes.</li></ul><p>In ASP.NET Core, we connect the interface to its implementation using the built-in DI container:</p><pre>builder.Services.AddScoped&lt;<br>    IOrderRepository,<br>    OrderRepository&gt;();</pre><p>Now, when CreateOrderHandler requests IOrderRepository, ASP.NET Core provides the registered implementation.</p><p>This allows the Application layer to stay independent of the actual database implementation.</p><h3>5. What Happens When a Request Arrives?</h3><p>Let’s follow the complete execution of POST /api/orders.</p><pre>Client sends HTTP request<br>          ↓<br>OrdersController<br>(Presentation)<br>          ↓<br>CreateOrderHandler<br>(Application)<br>          ↓<br>Order entity<br>(Domain — validates rules)<br>          ↓<br>IOrderRepository<br>(Application contract)<br>          ↓<br>OrderRepository<br>(Infrastructure — EF Core)<br>          ↓<br>SQL Server</pre><p>This diagram represents <strong>runtime execution flow</strong>, not source-code dependency direction.</p><p>At runtime, Application calls Infrastructure through an interface. At compile time, Infrastructure references Application.</p><p>That distinction is fundamental to understanding Clean Architecture.</p><h3>6. Why Is Clean Architecture Useful?</h3><p>The main benefits become visible as your application grows.</p><p><strong>Maintainability:</strong> Each layer has a clear responsibility, making code easier to understand and modify.</p><p><strong>Testability:</strong> Business rules can be tested without starting the web server or connecting to a real database.</p><p><strong>Flexibility:</strong> Infrastructure implementations can change while the business-facing contracts remain stable.</p><p><strong>Reduced coupling:</strong> Controllers don’t need to understand database operations, and domain entities don’t need to understand HTTP.</p><p>However, Clean Architecture doesn’t eliminate all complexity. Database migrations, transactions, performance, and integration concerns still require careful engineering.</p><h3>7. Common Mistakes to Avoid</h3><p><strong>Mistake 1: Thinking Clean Architecture is just four folders.</strong></p><p>Creating separate projects doesn’t guarantee a clean design. The important part is enforcing dependency boundaries.</p><p><strong>Mistake 2: Putting business rules inside controllers.</strong></p><p>Controllers should focus on HTTP concerns and delegate business operations to application use cases.</p><p><strong>Mistake 3: Creating interfaces for everything.</strong></p><p>Not every class needs an interface. Introduce abstractions when they establish useful boundaries.</p><p><strong>Mistake 4: Thinking CQRS or MediatR is mandatory.</strong></p><p>Clean Architecture can work perfectly well without either. They are additional patterns and tools, not requirements.</p><h3>Final Thoughts</h3><p>Clean Architecture isn’t about writing more code or creating more projects.</p><p>It’s about building an application where <strong>business logic is the priority, and technical details remain replaceable implementation choices</strong>.</p><p>The next time you design an ASP.NET Core application, don’t start by asking:</p><p><em>“How many layers should I create?”</em></p><p>Instead, ask:</p><p><strong>“Which parts of my application represent business decisions, and which parts are just implementation details?”</strong></p><p>That’s where good architectural thinking begins.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4a7763198834" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I’d Learn .NET From Scratch Without Getting Lost]]></title>
            <link>https://wiemksaier.medium.com/how-id-learn-net-from-scratch-in-2026-without-getting-lost-2f833369226c?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/2f833369226c</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[tech]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Tue, 15 Sep 2026 17:32:05 GMT</pubDate>
            <atom:updated>2026-09-15T17:32:55.969Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*n6TNP3BzCdJyKUz2" /><figcaption>Photo by <a href="https://unsplash.com/@tabeaschimpf?utm_source=medium&amp;utm_medium=referral">Tabea Schimpf</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When someone decides to learn .NET, the first thing they usually search for is:</p><blockquote><em>“.NET developer roadmap.”</em></blockquote><p>And then comes the problem.</p><p>You find a giant diagram with C#, ASP.NET Core, EF Core, SQL, Docker, Azure, design patterns, microservices, testing, CI/CD, Redis, RabbitMQ…</p><p>And suddenly learning .NET feels less like a roadmap and more like preparing for a final boss.</p><p>I’ve been working through my own .NET roadmap, and one thing became very clear:</p><p><strong>A good roadmap is not about learning everything. It is about learning things in the right order.</strong></p><p>Here is how I would approach it.</p><h3>1. Don’t Start With ASP.NET Core</h3><p>Yes, I said it.</p><p>A lot of people start their .NET journey by creating an API:</p><pre>app.MapGet(&quot;/users&quot;, () =&gt; users);</pre><p>It works.</p><p>Great.</p><p>But then something slightly more complicated appears and suddenly we don’t understand what C# is actually doing for us.</p><p>Before becoming an ASP.NET Core developer, become comfortable with <strong>C# itself</strong>.</p><p>And I don’t mean stopping after:</p><pre>if<br>for<br>while<br>class<br>interface</pre><p>Go deeper.</p><p>Understand the difference between a class and a struct.</p><p>Understand value types and reference types.</p><p>Understand what ref, out and in actually mean.</p><p>Learn why strings behave differently from many other reference types.</p><p>Understand immutability.</p><p>Learn the difference between shallow and deep copying.</p><p>Understand records and with expressions.</p><p>And eventually, learn a little about what happens underneath your code:</p><ul><li>Stack and heap</li><li>Garbage Collector</li><li>Boxing and unboxing</li><li>CLR</li><li>Object lifetime</li></ul><p>You don’t need to become a runtime engineer.</p><p>But you should stop seeing C# as magic.</p><p>That’s the first milestone.</p><h3>2. Learn the Parts of C# That Make Modern .NET Feel Like .NET</h3><p>Once the fundamentals are comfortable, move into the features you will see everywhere in real projects.</p><p>Delegates.</p><p>Action.</p><p>Func.</p><p>Predicate.</p><p>Events.</p><p>Lambdas.</p><p>At first, things like this can look strange:</p><pre>Func&lt;int, int, int&gt; add = (a, b) =&gt; a + b;</pre><p>But later you realize that delegates and lambdas are everywhere.</p><p>LINQ uses them.</p><p>Events use them.</p><p>Framework APIs use them.</p><p>Dependency injection configurations use them.</p><p>Once you understand them, a lot of .NET suddenly becomes easier to read.</p><h3>3. Don’t Just “Learn LINQ”</h3><p>LINQ deserves its own stage.</p><p>Most developers quickly learn:</p><pre>users.Where(x =&gt; x.IsActive).ToList();</pre><p>But LINQ is much bigger than Where().</p><p>Learn:</p><p>Select</p><p>SelectMany</p><p>OrderBy</p><p>GroupBy</p><p>Any</p><p>All</p><p>Skip</p><p>Take</p><p>Join</p><p>And especially learn this distinction:</p><pre>IEnumerable&lt;T&gt;</pre><p>vs.</p><pre>IQueryable&lt;T&gt;</pre><p>Because once Entity Framework Core enters the picture, that difference matters a lot.</p><p>You should start asking questions like:</p><p><strong>Is this operation happening in memory?</strong></p><p>or</p><p><strong>Will this become SQL?</strong></p><p>That’s when you’re no longer just memorizing LINQ methods.</p><p>You’re reasoning about your application.</p><h3>4. Async/Await: Don’t Stop at the Syntax</h3><p>Most tutorials teach async like this:</p><pre>await DoSomethingAsync();</pre><p>And then move on.</p><p>That’s not enough.</p><p>Learn what a Task actually represents.</p><p>Understand:</p><pre>Task.WhenAll()</pre><p>and:</p><pre>Task.WhenAny()</pre><p>Understand why this:</p><pre>.Result</pre><p>can be very different from:</p><pre>await</pre><p>Learn when parallelizing work makes sense and when it doesn’t.</p><p>Understand cancellation tokens.</p><p>Understand why asynchronous programming is so important for server applications.</p><p>You don’t need to memorize the internals of the Task Parallel Library.</p><p>But you should understand what your program is doing when it hits await.</p><h3>5. Learn Memory Before You Think You Need It</h3><p>This is one of those topics developers often postpone.</p><p>And I understand why.</p><p>Span&lt;T&gt; isn&#39;t exactly the first thing you need when building a Todo API.</p><p>But eventually, understanding memory makes you a much stronger .NET developer.</p><p>Start with:</p><pre>IDisposable</pre><p>Then understand:</p><pre>using</pre><p>Learn why some objects need deterministic cleanup.</p><p>Then slowly explore concepts such as:</p><pre>Span&lt;T&gt;<br>Memory&lt;T&gt;<br>ArrayPool&lt;T&gt;</pre><p>Again, don’t turn your roadmap into a PhD in memory management :)</p><p>The goal is awareness.</p><p>When performance problems eventually appear, these concepts won’t feel completely foreign.</p><h3>6. Now Learn ASP.NET Core</h3><p>Now the framework starts making much more sense.</p><p>Learn how an HTTP request travels through your application.</p><p>Learn:</p><ul><li>Routing</li><li>Controllers</li><li>Minimal APIs</li><li>Middleware</li><li>Dependency Injection</li><li>Configuration</li><li>Logging</li><li>Validation</li><li>Authentication</li><li>Authorization</li><li>Exception handling</li></ul><p>And don’t turn this into:</p><blockquote><em>Controllers vs Minimal APIs: WHICH ONE WINS?</em></blockquote><p>Both exist for a reason.</p><p>Minimal APIs can be excellent for small APIs, microservices, prototypes or endpoints where ceremony adds little value.</p><p>Controllers can be much easier to organize when applications become larger.</p><p>The goal isn’t to join a team.</p><p><strong>The goal is to understand the trade-offs.</strong></p><h3>7. Then Learn Entity Framework Core Properly</h3><p>At first EF Core looks beautiful.</p><pre>var users = await context.Users.ToListAsync();</pre><p>Database done.</p><p>Except… not really.</p><p>This is where another level of learning begins.</p><p>Understand what a DbContext represents.</p><p>Learn entity relationships.</p><p>Learn migrations.</p><p>Learn tracking vs no-tracking queries.</p><p>Understand:</p><pre>Include()</pre><p>Learn projections.</p><p>Understand when queries are executed.</p><p>And please learn to recognize this kind of problem:</p><pre>var users = await context.Users.ToListAsync();<br>var activeUsers = users.Where(x =&gt; x.IsActive);</pre><pre>versus:</pre><pre>var activeUsers = await context.Users<br>    .Where(x =&gt; x.IsActive)<br>    .ToListAsync();</pre><p>Those two pieces of code may produce the same result.</p><p>They don’t necessarily do the same amount of work.</p><p>That distinction matters.</p><h3>8. Build Something Before Adding More Boxes to the Roadmap</h3><p>This part is important.</p><p>Stop learning for a moment.</p><p>Build.</p><p>Not another application where the tutorial tells you what to type.</p><p>Build something where <strong>you make the decisions</strong>.</p><p>Add authentication.</p><p>Add pagination.</p><p>Add filtering.</p><p>Handle errors.</p><p>Add validation.</p><p>Use a real database.</p><p>Write tests.</p><p>Refactor ugly code.</p><p>Break something.</p><p>Fix it.</p><p>Because there is a point where another 4-hour course gives you less value than 30 minutes debugging your own mistake.</p><h3>9. Only Then Move Toward Architecture</h3><p>This is where roadmaps usually become dangerous.</p><p>People see:</p><p>Clean Architecture.</p><p>CQRS.</p><p>DDD.</p><p>Microservices.</p><p>Event-driven architecture.</p><p>And suddenly their Todo application has 14 projects, 37 interfaces and a message broker.</p><p>Architecture should solve problems.</p><p>Not create impressive folder structures.</p><p>Start with separation of concerns.</p><p>Understand dependency direction.</p><p>Understand why business logic shouldn’t live everywhere.</p><p>Learn SOLID.</p><p>Learn common design patterns.</p><p>Understand layered architecture.</p><p>Then explore Clean Architecture.</p><p>Later, study things like CQRS, event-driven architecture and microservices.</p><p>But learn the <strong>problem first</strong>.</p><p>Then learn the architecture that solves it.</p><p>Not the other way around.</p><h3>10. Learn the Things That Make Code Production-Ready</h3><p>At some point, writing code that works is no longer enough.</p><p>Now your roadmap should include:</p><p>Testing.</p><p>Logging.</p><p>Security.</p><p>Performance.</p><p>Caching.</p><p>Docker.</p><p>CI/CD.</p><p>Observability.</p><p>Git.</p><p>Code reviews.</p><p>Database optimization.</p><p>API versioning.</p><p>Background processing.</p><p>Messaging.</p><p>And eventually cloud platforms.</p><p>This is where you start moving from:</p><p><strong>“I can build a .NET application.”</strong></p><p>to:</p><p><strong>“I understand how a .NET application survives in production.”</strong></p><p>Those are very different skills.</p><h3>The Roadmap Is Not a Checklist</h3><p>This is probably the biggest thing I’ve learned while following my own roadmap.</p><p>You never really finish a topic.</p><p>I studied LINQ.</p><p>Then EF Core made me understand LINQ differently.</p><p>I studied async programming.</p><p>Then web development gave it more context.</p><p>I studied memory management.</p><p>Then performance made those concepts useful.</p><p>That’s how learning software engineering works.</p><p>You keep returning to the same concepts but every time, you understand them at a deeper level.</p><p>So my .NET roadmap isn’t:</p><p><strong>Learn → Check the box → Never touch again.</strong></p><p>It’s more like:</p><p><strong>Learn → Build → Struggle → Understand → Revisit → Improve.</strong></p><p>And if you’re starting your .NET journey, that’s probably the most important thing to remember.</p><p>Don’t try to learn the entire ecosystem.</p><p>Don’t chase every new library.</p><p>Don’t copy someone else’s 200-item roadmap.</p><p>Build strong foundations.</p><p>Add one layer at a time.</p><p>And most importantly:</p><p><strong>Don’t learn .NET just to know how to write .NET code.</strong></p><p>Learn enough to understand <strong>why the code works the way it does.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2f833369226c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I’m Not a Big Fan of Minimal APIs — But I Get Why They Exist]]></title>
            <link>https://wiemksaier.medium.com/im-not-a-big-fan-of-minimal-apis-but-i-get-why-they-exist-135c019c6554?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/135c019c6554</guid>
            <category><![CDATA[api]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[dotnet]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Tue, 01 Sep 2026 14:34:01 GMT</pubDate>
            <atom:updated>2026-09-01T14:34:01.525Z</atom:updated>
            <content:encoded><![CDATA[<p>I’ll admit it: <strong>I’m not really a fan of Minimal APIs.</strong></p><p>When I work on a reasonably large ASP.NET Core API, I still like opening a ProductsController, seeing the actions, the dependencies, and having a clear structure.</p><p>But that doesn’t mean Minimal APIs are useless.</p><p>In some cases, they actually make a lot of sense.</p><h3>So, What Is a Minimal API?</h3><p>With controllers, an endpoint usually looks like this:</p><pre>[ApiController]<br>[Route(&quot;api/products&quot;)]<br>public class ProductsController : ControllerBase<br>{<br>    private readonly IProductService _service;<br>    public ProductsController(IProductService service)<br>    {<br>        _service = service;<br>    }<br>    [HttpGet(&quot;{id}&quot;)]<br>    public async Task&lt;IActionResult&gt; Get(int id)<br>    {<br>        var product = await _service.GetByIdAsync(id);<br>        return product is null<br>            ? NotFound()<br>            : Ok(product);<br>    }<br>}</pre><p>With Minimal APIs, the same idea can become:</p><pre>app.MapGet(&quot;/api/products/{id}&quot;, async (<br>    int id,<br>    IProductService service) =&gt;<br>{<br>    var product = await service.GetByIdAsync(id);<br>   return product is null<br>        ? Results.NotFound()<br>        : Results.Ok(product);<br>});</pre><p>The main difference is simple.</p><p>With controllers:</p><pre>Route → Controller → Action → Service</pre><p>With Minimal APIs:</p><pre>Route → Handler → Service</pre><p>The controller is no longer required.</p><h3>Minimal Does Not Mean “Everything in Program.cs”</h3><p>This is probably the part that made me dislike Minimal APIs at first.</p><p>When you see code like:</p><pre>app.MapGet(...);<br>app.MapPost(...);<br>app.MapPut(...);<br>app.MapDelete(...);</pre><p>it’s easy to imagine Program.cs becoming a 2,000-line monster.</p><p>But that’s not a Minimal API requirement.</p><p>You can still organize your endpoints:</p><pre>Endpoints/<br>├── ProductEndpoints.cs<br>├── OrderEndpoints.cs<br>└── UserEndpoints.cs</pre><p>and keep Program.cs simple:</p><pre>app.MapProductEndpoints();<br>app.MapOrderEndpoints();<br>app.MapUserEndpoints();</pre><p>So Minimal APIs remove some HTTP ceremony.</p><p>They don’t remove architecture.</p><h3>When Do They Actually Make Sense?</h3><p>This is where I understand their value.</p><p>Imagine a small microservice exposing only:</p><pre>POST /notifications<br>GET  /notifications/{id}<br>GET  /health</pre><p>Do we really need an entire controller structure for three endpoints?</p><p>Maybe not.</p><p>Minimal APIs are great for:</p><ul><li>small APIs</li><li>microservices</li><li>internal endpoints</li><li>simple CRUD services</li><li>vertical slice architectures</li></ul><p>For a large API with many conventions and a big team, I might still prefer controllers.</p><p>And that’s fine.</p><h3>Controllers vs Minimal APIs Is Not a Competition</h3><p>I don’t think the question should be:</p><blockquote><em>“Should we stop using controllers?”</em></blockquote><p>A better question is:</p><blockquote><em>“Does this endpoint actually need a controller?”</em></blockquote><p>Sometimes the answer is yes.</p><p>Sometimes this is enough:</p><pre>app.MapGet(&quot;/health&quot;, () =&gt; &quot;OK&quot;);</pre><p>You can even use both in the same application:</p><pre>app.MapControllers();<br>app.MapGet(&quot;/health&quot;, () =&gt; &quot;OK&quot;);</pre><h3>My Take</h3><p>Minimal APIs still aren’t my default choice for every project.</p><p>I like the structure and conventions that controllers give me.</p><p>But I also understand why Minimal APIs exist now.</p><p>They’re not about writing the smallest amount of code possible.</p><p>They’re about avoiding structure when that structure isn’t actually giving us much value.</p><p>So no, Minimal APIs haven’t converted me.</p><p>But for the right project?</p><p>I’d definitely use them.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=135c019c6554" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding Garbage Collector and Finalizers in C#]]></title>
            <link>https://wiemksaier.medium.com/understanding-garbage-collector-and-finalizers-in-c-1497b72cd140?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/1497b72cd140</guid>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[garbage-collector]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Mon, 13 Jul 2026 07:21:24 GMT</pubDate>
            <atom:updated>2026-07-13T07:21:24.554Z</atom:updated>
            <content:encoded><![CDATA[<p>When we write code in C#, we create objects all the time.</p><pre>var user = new User();</pre><p>But here is an important question:</p><p><strong>Who removes this object from memory when we no longer need it?</strong></p><p>In languages like C or C++, developers often have to manually free memory. But in C#, this job is mostly handled automatically by the <strong>Garbage Collector</strong>, usually called the <strong>GC</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2p8g2-DlgVNBqLPs" /><figcaption>Photo by <a href="https://unsplash.com/@gary_at_unsplash?utm_source=medium&amp;utm_medium=referral">Gary Chan</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>What Is the Garbage Collector?</h3><p>The Garbage Collector is a part of the .NET runtime that manages memory for us.</p><p>Its main job is simple:</p><p><strong>Find objects that are no longer used and remove them from memory.</strong></p><p>For example:</p><pre>public void CreateUser()<br>{<br>    var user = new User();<br>}</pre><p>When the method finishes, the variable user is gone. If nothing else is referencing that object, the Garbage Collector can clean it later.</p><p>But there is one important thing to understand:</p><p>The GC does <strong>not</strong> clean memory immediately when the object is no longer used. It decides the best time to do it.</p><h3>Why Do We Need the Garbage Collector?</h3><p>Without the Garbage Collector, we would have to manually release memory every time we finish using an object.</p><p>That can easily cause problems like:</p><ul><li>Memory leaks</li><li>Crashes</li><li>Using objects after they were already destroyed</li><li>Bad memory management</li></ul><p>The Garbage Collector makes memory management safer and easier for developers.</p><p>In most cases, we do not need to think about removing normal objects from memory. The GC will take care of them automatically.</p><h3>Managed Memory vs External Resources</h3><p>The Garbage Collector is very good at cleaning <strong>managed memory</strong>.</p><p>Managed memory means normal objects created and managed by .NET, like:</p><pre>var list = new List&lt;string&gt;();<br>var user = new User();</pre><p>But sometimes, an object uses something outside normal .NET memory, such as:</p><ul><li>Files</li><li>Database connections</li><li>Network connections</li><li>Streams</li></ul><p>Example:</p><pre>var file = new FileStream(&quot;test.txt&quot;, FileMode.Open);</pre><p>These resources are not just simple C# objects. They are connected to the operating system.</p><p>This is where cleanup becomes more important.</p><h3>What Is a Finalizer?</h3><p>A finalizer is a special method that runs before an object is removed by the Garbage Collector.</p><p>It looks like this:</p><pre>public class MyResource<br>{<br>    ~MyResource()<br>    {<br>        // Cleanup logic<br>    }<br>}</pre><p>The method ~MyResource() is called a <strong>finalizer</strong>.</p><p>Its purpose is to give the object one last chance to clean something before it is removed from memory.</p><h3>Should We Use Finalizers Often?</h3><p>Usually, no.</p><p>Finalizers are rarely written manually in modern C# code.</p><p>Why?</p><p>Because finalizers make the cleanup process slower and more complex.</p><p>When an object has a finalizer, the Garbage Collector cannot remove it directly. It first needs to call the finalizer, then the object will be cleaned later.</p><p>So finalizers should only be used in special cases, usually when a class directly owns unmanaged resources.</p><p>For most real projects, we use something better: <strong>IDisposable</strong>.</p><h3>IDisposable Is the Preferred Way</h3><p>When a class uses resources like files, streams, or database connections, it should implement IDisposable.</p><p>Example:</p><pre>public class FileService : IDisposable<br>{<br>    private FileStream fileStream;<br><br>    public FileService()<br>    {<br>        fileStream = new FileStream(&quot;test.txt&quot;, FileMode.Open);<br>    }<br>    public void Dispose()<br>    {<br>        fileStream.Dispose();<br>    }<br>}</pre><p>Then we can use it like this:</p><pre>using var service = new FileService();</pre><p>The using statement automatically calls Dispose() when the object is no longer needed.</p><p>This is better because the cleanup happens immediately and predictably.</p><h3>Simple Rule to Remember</h3><p>If your object only uses normal C# objects, you usually do not need to do anything.</p><p>The Garbage Collector will handle it.</p><p>But if your object uses files, streams, database connections, or external resources, you should use IDisposable and the using statement.</p><p>Finalizers exist, but they are for rare and advanced cases.</p><h3>Conclusion</h3><p>The Garbage Collector is one of the most important features of .NET. It helps developers avoid many memory management problems by automatically cleaning unused objects.</p><p>But the GC does not replace good resource management.</p><p>For normal objects, let the Garbage Collector do its job.</p><p>For external resources, use Dispose() and using.</p><p>And remember:</p><p><strong>Finalizers exist, but in most real projects, IDisposable is the better tool.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1497b72cd140" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Heap vs Stack in C#: The Biggest Misconception Every Beginner Learns]]></title>
            <link>https://wiemksaier.medium.com/heap-vs-stack-in-c-the-biggest-misconception-every-beginner-learns-96af92ee5ca7?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/96af92ee5ca7</guid>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[csharp]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Sun, 28 Jun 2026 22:29:54 GMT</pubDate>
            <atom:updated>2026-06-28T22:32:54.761Z</atom:updated>
            <content:encoded><![CDATA[<p>If you’ve been learning C#, you’ve probably heard this:</p><blockquote><strong><em>Structs are stored on the Stack, and Classes are stored on the Heap.</em></strong></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BxNeSUdm5NqJq-jQ" /><figcaption>Photo by <a href="https://unsplash.com/@towfiqu999999?utm_source=medium&amp;utm_medium=referral">Towfiqu barbhuiya</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>It’s simple.</p><p>It’s easy to remember.</p><p>And… it’s not entirely true.</p><p>Let’s fix that in less than 3 minutes.</p><h3>The Stack: Fast and Temporary</h3><p>Think of the stack as your method’s workspace.</p><p>When a method starts, it gets a small area to store local variables and parameters.</p><pre>void Calculate()<br>{<br>    int x = 10;<br>    int y = 20;<br>}</pre><p>When Calculate() finishes, both x and y disappear automatically.</p><p>No cleanup.<br>No Garbage Collector.<br>Just incredibly fast memory management.</p><h3>The Heap: Where Objects Live</h3><p>Now look at this:</p><pre>Person person = new Person();</pre><p>Many developers think <strong>person is on the Heap.</strong></p><p>Not exactly.</p><p>The <strong>object</strong> is on the Heap.</p><p>The <strong>variable </strong><strong>person</strong> is just a reference that points to that object.</p><p>You can imagine it like this:</p><pre>Stack                    Heap<br>-------                  ----------------<br>person  -------------&gt;   Person Object</pre><p>The variable knows <em>where</em> the object is — it doesn’t contain the object itself.</p><h3>The Rule That Actually Matters</h3><p>Instead of memorizing:</p><p>❌ Struct → Stack</p><p>❌ Class → Heap</p><p>Remember this instead:</p><ul><li><strong>Value types are copied by value.</strong></li><li><strong>Reference types are copied by reference.</strong></li></ul><p>That’s the real difference.</p><p>For example:</p><pre>int x = 10;<br>int y = x;</pre><pre>y = 20;</pre><p>x is still 10 because the value was copied.</p><p>Now compare that with a class:</p><pre>Person p1 = new Person();<br>Person p2 = p1;</pre><pre>p2.Name = &quot;Alice&quot;;</pre><p>p1.Name is also &quot;Alice&quot; because both variables point to the <strong>same object</strong>.</p><h3>The Misconception</h3><p>Here’s the part that surprises many developers.</p><p>Consider this struct:</p><pre>struct Point<br>{<br>    public int X;<br>    public int Y;<br>}</pre><p>Now place it inside a class:</p><pre>class Car<br>{<br>    public Point Position;<br>}</pre><p>Is Point on the Stack?</p><p>No.</p><p>Since it’s a field inside a heap-allocated object (Car), the struct is also stored in the Heap.</p><p>So the statement:</p><blockquote><em>“Structs are always stored on the Stack.”</em></blockquote><p>is simply false.</p><h3>Takeaway</h3><p>The Stack and the Heap are memory areas.</p><p>struct and class are language features.</p><p>They are related, but they’re <strong>not the same concept</strong>.</p><p>The best mental model is this:</p><ul><li><strong>Stack and Heap describe where memory is allocated.</strong></li><li><strong>Value and Reference types describe how data is copied and passed around.</strong></li></ul><p>Once you understand that distinction, concepts like boxing, garbage collection, ref, in, out, and even performance optimizations become much easier to grasp.</p><p>Sometimes replacing one incorrect sentence with the right mental model is worth more than memorizing ten definitions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=96af92ee5ca7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I Ignored @key in Blazor Until It Broke My UI]]></title>
            <link>https://wiemksaier.medium.com/i-ignored-key-in-blazor-until-it-broke-my-ui-b9f1b8081c2d?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/b9f1b8081c2d</guid>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[blazor]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[blazor-components]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Sun, 14 Jun 2026 17:43:26 GMT</pubDate>
            <atom:updated>2026-06-14T17:43:26.539Z</atom:updated>
            <content:encoded><![CDATA[<p>If you’ve been working with Blazor for a while, you’ve probably written code like this dozens of times:</p><pre>@foreach (var user in Users)<br>{<br>    &lt;UserCard User=&quot;user&quot; /&gt;<br>}</pre><p>Simple. Clean. Works perfectly.</p><p>A while ago, I was working on a page that displayed a dynamic list of items. Everything seemed fine until I started testing scenarios where items could be added, removed, or reordered.</p><p>That’s when some strange behavior appeared:</p><ul><li>An input field would unexpectedly lose focus while I was typing.</li><li>Components seemed to keep state that belonged to a different item.</li><li>Expanded sections would collapse after a list update.</li><li>UI state appeared to “move” from one item to another.</li></ul><p>The code looked correct, but the behavior felt completely random.</p><p>The culprit :) ?</p><p>A small Blazor directive I had ignored:</p><pre>@key</pre><p>And once I understood what it actually does, many rendering mysteries suddenly made sense.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4jWr6Z8VlhIDOGol" /><figcaption>Photo by <a href="https://unsplash.com/@villxsmil?utm_source=medium&amp;utm_medium=referral">Luis Villasmil</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>How Blazor Thinks</h3><p>When a component re-renders, Blazor tries to be smart.</p><p>Instead of destroying and recreating every DOM element and component, it compares the old render tree with the new one and reuses as much as possible.</p><p>This optimization is great for performance.</p><p>However, Blazor often identifies elements by their position in a list.</p><p>Consider this example:</p><pre>@foreach (var person in People)<br>{<br>    &lt;PersonCard Person=&quot;person&quot; /&gt;<br>}</pre><p>Initially the list contains:</p><pre>Alice<br>Bob<br>Charlie</pre><p>Now imagine we insert a new item at the beginning:</p><pre>People.Insert(0, new Person(&quot;David&quot;));</pre><p>The new list becomes:</p><pre>David<br>Alice<br>Bob<br>Charlie</pre><p>From your perspective, a new item was added.</p><p>From Blazor’s perspective, every position changed.</p><p>As a result, Blazor may reuse existing component instances for different people.</p><p>That means:</p><ul><li>The component that represented Alice may now represent David.</li><li>The component that represented Bob may now represent Alice.</li><li>Internal state may be reused unexpectedly.</li></ul><p>And that’s where weird bugs begin.</p><h3>Enter @key</h3><p>The solution is surprisingly simple:</p><pre>@foreach (var person in People)<br>{<br>    &lt;PersonCard<br>        @key=&quot;person.Id&quot;<br>        Person=&quot;person&quot; /&gt;<br>}</pre><p>Now Blazor no longer tracks components by position.</p><p>Instead, it tracks them by identity.</p><p>You’re effectively telling Blazor:</p><blockquote><em>This component belongs to this specific object. Preserve that relationship.</em></blockquote><p>If a new item is inserted, Blazor knows exactly which component belongs to which person.</p><p>No confusion.</p><p>No state swapping.</p><p>No surprises.</p><h3>A Real-World Example</h3><p>Let’s say you’re building a form.</p><pre>@foreach (var child in Children)<br>{<br>    &lt;input value=&quot;@child.Name&quot; /&gt;<br>}</pre><p>A user starts typing in the second input.</p><p>Meanwhile, another user adds a new child, causing the list to refresh.</p><p>Without @key, Blazor may reuse DOM elements and component instances based on position.</p><p>The result can be:</p><ul><li>Lost cursor position</li><li>Lost focus</li><li>Data appearing in the wrong place</li></ul><p>Adding a key fixes the issue:</p><pre>@foreach (var child in Children)<br>{<br>    &lt;input<br>        @key=&quot;child.Id&quot;<br>        value=&quot;@child.Name&quot; /&gt;<br>}</pre><p>Now each input remains attached to the correct child regardless of how the list changes.</p><h3>When Should You Use @key?</h3><p>You should strongly consider using @key whenever:</p><h4>1. Rendering Dynamic Lists</h4><pre>@foreach (var product in Products)<br>{<br>    &lt;ProductCard<br>        @key=&quot;product.Id&quot;<br>        Product=&quot;product&quot; /&gt;<br>}</pre><h4>2. Items Can Be Added or Removed</h4><pre>Products.Insert(0, newProduct);<br>Products.Remove(product);</pre><h4>3. Components Have Internal State</h4><pre>&lt;UserEditor<br>    @key=&quot;user.Id&quot;<br>    User=&quot;user&quot; /&gt;</pre><p>Stateful components are particularly vulnerable to incorrect reuse.</p><h4>4. User Experience Matters</h4><p>Forms, editable grids, drag-and-drop interfaces, and dashboards are all excellent candidates.</p><h3>When You Don’t Need It</h3><p>Not every list requires @key.</p><p>For example:</p><pre>@foreach (var month in Months)<br>{<br>    &lt;span&gt;@month&lt;/span&gt;<br>}</pre><p>If the list never changes and contains simple elements, adding keys usually brings little benefit.</p><p>The important thing is understanding when identity matters.</p><h3>A Common Mistake</h3><p>I’ve seen developers do this:</p><pre>@key=&quot;Guid.NewGuid()&quot;</pre><p>This is almost always wrong.</p><p>Why?</p><p>Because the value changes on every render.</p><p>Blazor sees a completely new key every time and destroys then recreates the component from scratch.</p><p>You lose the very optimization that @key was designed to provide.</p><p>Keys should be stable.</p><p>Good examples:</p><pre>@key=&quot;user.Id&quot;<br>@key=&quot;product.Id&quot;<br>@key=&quot;order.OrderNumber&quot;</pre><p>Bad examples:</p><pre>@key=&quot;Guid.NewGuid()&quot;<br>@key=&quot;DateTime.Now&quot;</pre><h3>The Lesson</h3><p>@key is one of those Blazor features that looks insignificant until you understand the problem it solves.</p><p>Most applications work perfectly without it — until lists become dynamic, components become stateful, and users start interacting with your UI.</p><p>Then suddenly a single line:</p><pre>@key=&quot;item.Id&quot;</pre><p>can save hours of debugging.</p><p>If you’re building interactive Blazor applications and you’re not paying attention to component identity, you’re likely one bug away from discovering why @key exists.</p><p>And when that day comes, you’ll be glad you know it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b9f1b8081c2d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Most Underrated LINQ Feature in .NET: ToLookup()]]></title>
            <link>https://wiemksaier.medium.com/the-most-underrated-linq-feature-in-net-tolookup-c7737474e28d?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/c7737474e28d</guid>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Mon, 25 May 2026 14:49:04 GMT</pubDate>
            <atom:updated>2026-05-25T14:49:04.041Z</atom:updated>
            <content:encoded><![CDATA[<p>Most C# developers know:</p><ul><li>List</li><li>Dictionary</li><li>HashSet</li><li>GroupBy</li></ul><p>But there’s one LINQ feature that almost nobody talks about:</p><pre>ToLookup()</pre><p>Honestly, many developers go years without using it once.</p><p>And that’s surprising because ILookup&lt;TKey, TValue&gt; solves a very common problem elegantly.</p><h3>The Problem: One Key → Multiple Values</h3><p>A Dictionary&lt;TKey, TValue&gt; works when:</p><ul><li>one key maps to one value</li></ul><p>Example:</p><pre>Dictionary&lt;int, User&gt;</pre><p>But real applications often need:</p><ul><li>one department → many employees</li><li>one category → many products</li><li>one parent → many children</li><li>one order → many items</li></ul><p>Most developers solve this manually:</p><pre>Dictionary&lt;int, List&lt;Employee&gt;&gt;</pre><p>It works.</p><p>But .NET already has a structure designed exactly for this scenario:</p><pre>ILookup&lt;TKey, TValue&gt;</pre><h3>Creating a Lookup</h3><p>Imagine this model:</p><pre>public class Employee<br>{<br>    public int Id { get; set; }<br>    public string Name { get; set; } = string.Empty;<br>    public int DepartmentId { get; set; }<br>}</pre><p>Now let’s group employees by department:</p><pre>var employees = new List&lt;Employee&gt;<br>{<br>    new() { Id = 1, Name = &quot;Alice&quot;, DepartmentId = 1 },<br>    new() { Id = 2, Name = &quot;Bob&quot;, DepartmentId = 1 },<br>    new() { Id = 3, Name = &quot;Charlie&quot;, DepartmentId = 2 }<br>};<br><br>ILookup&lt;int, Employee&gt; lookup =<br>    employees.ToLookup(e =&gt; e.DepartmentId);</pre><p>Now:</p><ul><li>key = DepartmentId</li><li>value = employees belonging to that department</li></ul><h3>Accessing Values</h3><pre>var hrEmployees = lookup[1];</pre><p>Result:</p><pre>Alice<br>Bob</pre><p>You immediately get all employees for department 1.</p><p>No filtering.<br> No manual grouping.<br> No dictionary initialization.<br> No ContainsKey.</p><h3>The Difference Between GroupBy() and ToLookup()</h3><p>This is where many developers get confused.</p><p>At first glance, they look similar.</p><pre>employees.GroupBy(e =&gt; e.DepartmentId)</pre><p>vs</p><pre>employees.ToLookup(e =&gt; e.DepartmentId)</pre><p>But internally, they behave differently.</p><h3>GroupBy() → Deferred Execution</h3><p>GroupBy() is lazy.</p><p>It does not immediately build the groups.</p><p>The query executes when you enumerate it.</p><pre>var grouped = employees.GroupBy(e =&gt; e.DepartmentId);</pre><p>Nothing happens yet.</p><p>Execution occurs later:</p><pre>foreach (var group in grouped)<br>{<br>}</pre><h3>ToLookup() → Immediate Execution</h3><p>ToLookup() executes immediately.</p><pre>var lookup = employees.ToLookup(e =&gt; e.DepartmentId);</pre><p>The lookup structure is built instantly and cached in memory.</p><p>This makes repeated access very efficient.</p><h3>Why ToLookup() Can Be Faster</h3><p>Imagine this code:</p><pre>foreach (var department in departments)<br>{<br>    var employeesInDepartment =<br>        employees.Where(e =&gt; e.DepartmentId == department.Id);<br>// Process employees<br>}</pre><p>This repeatedly scans the entire employee list.</p><p>Now compare it with:</p><pre>var lookup = employees.ToLookup(e =&gt; e.DepartmentId);<br>foreach (var department in departments)<br>{<br>    var employeesInDepartment = lookup[department.Id];<br>    // Process employees<br>}</pre><p>Now access becomes extremely fast because the grouping is already indexed.</p><p>This is one of the best use cases for ToLookup().</p><h3>ILookup vs Dictionary&lt;TKey, List&lt;T&gt;&gt;</h3><p>A lot of developers ask:</p><blockquote><em>“Why not just use Dictionary&lt;int, List&lt;Employee&gt;&gt;?”</em></blockquote><p>Fair question.</p><p>Both solve similar problems.</p><p>But ILookup has some nice advantages.</p><h3>1. Cleaner Creation</h3><p>Dictionary approach:</p><pre>var dictionary = employees<br>    .GroupBy(e =&gt; e.DepartmentId)<br>    .ToDictionary(<br>        g =&gt; g.Key,<br>        g =&gt; g.ToList());</pre><p>Lookup approach:</p><pre>var lookup = employees.ToLookup(e =&gt; e.DepartmentId);</pre><p>Much simpler.</p><h3>2. Missing Keys Are Safe</h3><p>Dictionary:</p><pre>dictionary[999]</pre><p>Throws:</p><pre>KeyNotFoundException</pre><p>Lookup:</p><pre>lookup[999]</pre><p>Returns:</p><pre>Empty collection</pre><p>This is one of the most underrated benefits.</p><h3>3. Immutable Structure</h3><p>ILookup is read-only.</p><p>You cannot accidentally modify it after creation.</p><p>That makes it safer for:</p><ul><li>caching</li><li>read-heavy operations</li><li>query results</li><li>shared data</li></ul><h3>When Should You Use ToLookup()?</h3><p>ToLookup() shines when:</p><ul><li>you need repeated grouped access</li><li>the data is mostly read-only</li><li>you want fast retrieval by key</li><li>one key maps to multiple values</li><li>you want cleaner code than Dictionary&lt;TKey, List&lt;T&gt;&gt;</li></ul><p>Examples:</p><ul><li>grouping notifications by user</li><li>products by category</li><li>orders by customer</li><li>logs by severity</li><li>children by parent ID</li></ul><h3>Why Most Developers Never Use It</h3><p>Honestly?</p><p>Because it’s rarely taught.</p><p>Most tutorials focus on:</p><ul><li>List</li><li>Dictionary</li><li>HashSet</li></ul><p>And when developers need grouping, they usually stop at:</p><pre>GroupBy()</pre><p>ILookup sits in that strange area of .NET:</p><ul><li>extremely useful</li><li>very clean</li><li>surprisingly unknown</li></ul><h3>Final Thoughts</h3><p>ToLookup() is not something you’ll use every day.</p><p>But when you need:</p><ul><li>one-to-many relationships</li><li>fast grouped access</li><li>clean read-only grouping</li></ul><p>…it becomes one of the nicest LINQ features available.</p><p>Sometimes the best parts of .NET are the APIs nobody talks about enough.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c7737474e28d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[C# Pattern Matching — The Feature That Quietly Changed C#]]></title>
            <link>https://wiemksaier.medium.com/c-pattern-matching-the-feature-that-quietly-changed-c-7b56a0d341ed?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/7b56a0d341ed</guid>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Sun, 10 May 2026 22:43:47 GMT</pubDate>
            <atom:updated>2026-05-10T22:43:47.890Z</atom:updated>
            <content:encoded><![CDATA[<p>Most developers discover pattern matching with something simple like:</p><pre>if (obj is string text)<br>{<br>    Console.WriteLine(text);<br>}</pre><p>But modern C# turned pattern matching into something much bigger.</p><p>Today, pattern matching is one of the cleanest ways to:</p><ul><li>remove unnecessary null checks</li><li>reduce casting</li><li>simplify conditions</li><li>write more expressive code</li></ul><p>And the best part?</p><p>Most developers still use only 10% of it.</p><p>Let’s explore the most useful pattern matching features introduced from C# 7 to C# 12.</p><h3>C# 7 — The Beginning</h3><h4>Type Pattern</h4><pre>if (obj is string text)<br>{<br>    Console.WriteLine(text.Length);<br>}</pre><p>Before:</p><pre>if (obj is string)<br>{<br>    string text = (string)obj;<br>}</pre><p>C# 7 introduced:</p><ul><li>type checking</li><li>casting</li><li>variable declaration</li></ul><p>…all in one line.</p><p>This was the start of modern pattern matching.</p><h3>C# 8 — Cleaner Switch Logic</h3><h4>Switch Expressions</h4><p>Before:</p><pre>switch (status)<br>{<br>    case &quot;Done&quot;:<br>        return &quot;Finished&quot;;<br>    case &quot;Pending&quot;:<br>        return &quot;Waiting&quot;;<br>    default:<br>        return &quot;Unknown&quot;;<br>}</pre><p>After:</p><pre>return status switch<br>{<br>    &quot;Done&quot; =&gt; &quot;Finished&quot;,<br>    &quot;Pending&quot; =&gt; &quot;Waiting&quot;,<br>    _ =&gt; &quot;Unknown&quot;<br>};</pre><p>This feature immediately made code:</p><ul><li>shorter</li><li>cleaner</li><li>easier to read</li></ul><p>You’ll now find switch expressions everywhere in modern .NET applications.</p><h3>C# 9 — The Real Game Changer</h3><p>C# 9 introduced the patterns most developers use today.</p><h4>Non-null Pattern</h4><pre>if (user is { } u)<br>{<br>    Console.WriteLine(u.Name);<br>}</pre><p>Equivalent to:</p><pre>if (user != null)</pre><p>But cleaner because it also creates a local variable.</p><p>This pattern became extremely popular in:</p><ul><li>Blazor</li><li>ASP.NET Core</li><li>APIs</li><li>services</li></ul><h4>Logical Patterns</h4><pre>if (age is &gt; 18 and &lt; 30)<br>{<br>    Console.WriteLine(&quot;Young adult&quot;);<br>}</pre><pre>if (status is &quot;Done&quot; or &quot;Completed&quot;)<br>{<br>    Console.WriteLine(&quot;Finished&quot;);<br>}</pre><pre>if (value is not null)<br>{<br>    Console.WriteLine(&quot;Exists&quot;);<br>}</pre><p>These patterns make conditions read almost like English.</p><h4>Property Patterns</h4><pre>if (user is { IsActive: true, Role: &quot;Admin&quot; })<br>{<br>    // valid admin<br>}</pre><p>Instead of writing:</p><pre>if (user != null &amp;&amp;<br>    user.IsActive &amp;&amp;<br>    user.Role == &quot;Admin&quot;)</pre><p>You simply describe the shape of valid data.</p><p>That’s the real power of pattern matching.</p><h4>Nested Property Patterns</h4><pre>if (order is { Customer.Name: &quot;Wiem&quot; })<br>{<br>    // matched<br>}</pre><p>This allows deep object validation without complicated null checks.</p><h3>C# 11 — List Patterns</h3><p>C# 11 added support for collection matching.</p><h4>Exact Match</h4><pre>if (numbers is [1, 2, 3])<br>{<br>    Console.WriteLine(&quot;Exact match&quot;);<br>}</pre><h4>Start / End Matching</h4><pre>if (numbers is [1, ..])<br>{<br>    Console.WriteLine(&quot;Starts with 1&quot;);<br>}<br><br><br>if (numbers is [.., 10])<br>{<br>    Console.WriteLine(&quot;Ends with 10&quot;);<br>}</pre><p>This feature is incredibly useful for:</p><ul><li>parsing</li><li>routing</li><li>command processing</li><li>data validation</li></ul><h3>Real-World Example</h3><p>Here’s a practical example combining multiple patterns:</p><pre>if (request is<br>    {<br>        User: { IsActive: true, Role: &quot;Admin&quot; },<br>        Total: &gt; 100<br>    } validRequest)<br>{<br>    Process(validRequest);<br>}</pre><p>In a single expression, we:</p><ul><li>validate the user</li><li>check the role</li><li>verify activity status</li><li>validate the total</li><li>create a strongly typed variable</li></ul><p>Without nested conditions.<br>Without extra null checks.<br>Without casting.</p><h3>Final Thought</h3><p>Pattern matching is more than syntax sugar.</p><p>It changes the way you think about conditions.</p><p>Instead of writing:<br>“How do I check this?”</p><p>You start writing:<br>“What does valid data look like?”</p><p>And that small mindset shift makes modern C# code dramatically cleaner.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7b56a0d341ed" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[.NET Threads, Tasks & async/await — What Actually Happens Under the Hood]]></title>
            <link>https://wiemksaier.medium.com/net-threads-tasks-async-await-what-actually-happens-under-the-hood-a3dc7524bcbd?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/a3dc7524bcbd</guid>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[asyncawait]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Sun, 26 Apr 2026 22:58:25 GMT</pubDate>
            <atom:updated>2026-04-26T23:07:06.555Z</atom:updated>
            <content:encoded><![CDATA[<p>Most developers use async/await daily.</p><p>But if you ask:</p><blockquote><em>“On which thread does this run?”<br> “When is a thread created?”<br> “What does a Task really represent?”</em></blockquote><p>Things get… blurry.</p><p>This post is not about syntax.</p><p>It’s about building a <strong>correct mental model</strong> of how .NET executes your code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*yONUiWP6NSlQwCh0" /><figcaption>Photo by <a href="https://unsplash.com/@indraddd?utm_source=medium&amp;utm_medium=referral">Indra Utama</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>1. Threads — The Physical Reality</h3><p>At the lowest level, everything runs on a <strong>thread</strong>.</p><p>👉 A thread is:</p><ul><li>an OS-level execution unit</li><li>scheduled by the operating system</li><li>relatively <strong>expensive</strong> to create and manage</li></ul><p>If you do this:</p><pre>new Thread(() =&gt; DoWork()).Start();</pre><p>You are:</p><ul><li>allocating memory (stack)</li><li>asking the OS scheduler to manage it</li><li>increasing context switching overhead</li></ul><p>👉 This is why raw threads are rarely used in modern .NET.</p><h3>2. The Thread Pool — The Real Engine</h3><p>Instead of creating threads, .NET maintains a <strong>Thread Pool</strong>:</p><ul><li>a set of reusable worker threads</li><li>dynamically adjusted based on load</li><li>optimized to reduce thread creation/destruction</li></ul><p>When you write:</p><pre>Task.Run(() =&gt; DoWork());</pre><p>You are <strong>not creating a thread</strong>.</p><p>👉 You are <strong>queuing work</strong> to the thread pool.</p><p>.NET decides:</p><ul><li>when to execute it</li><li>on which thread</li><li>whether to create new threads</li></ul><h3>3. Task — Not a Thread, Not Just “Async”</h3><p>A Task is often misunderstood.</p><p>👉 A Task is:</p><blockquote><em>“A representation of an asynchronous operation that may complete in the future.”</em></blockquote><p>It is:</p><ul><li><strong>not a thread</strong></li><li><strong>not necessarily parallel</strong></li><li>a <strong>state machine + completion signal</strong></li></ul><p>It can represent:</p><ul><li>CPU work (via Task.Run)</li><li>I/O operations (HTTP, DB, file system)</li><li>already completed work (Task.FromResult)</li></ul><h3>4. async/await — A Compiler Transformation</h3><p>This is where things get interesting.</p><p>When you write:</p><pre>var data = await GetDataAsync();</pre><p>The compiler transforms your method into a <strong>state machine</strong>.</p><p>Conceptually:</p><ol><li>Start executing synchronously</li><li>Hit await</li><li>If not completed:</li></ol><ul><li>store the current state</li><li>return control to caller</li></ul><p>4. When task completes:</p><ul><li>resume execution from that exact point</li></ul><p>👉 No thread is “paused”<br> 👉 The method is <strong>split into continuation pieces</strong></p><h3>5. I/O vs CPU — The Critical Distinction</h3><h4>I/O-bound work (most async code)</h4><pre>await httpClient.GetAsync(url);</pre><p>👉 No thread is actively working<br> 👉 The OS handles the operation<br> 👉 A thread is only used:</p><ul><li>to start the request</li><li>to resume when it’s done</li></ul><h4>CPU-bound work</h4><pre>await Task.Run(() =&gt; Compute());</pre><p>👉 A thread <strong>is actively used</strong><br> 👉 This is real parallel execution</p><h3>6. Concurrency vs Parallelism</h3><p>These are often confused.</p><h3>Concurrency</h3><pre>var t1 = GetDataAsync();<br>var t2 = GetDataAsync();<br>await Task.WhenAll(t1, t2);</pre><p>👉 Multiple operations in progress<br> 👉 May NOT use multiple threads</p><h3>Parallelism</h3><pre>Parallel.For(0, 10, i =&gt; Compute(i));</pre><p>👉 Multiple threads executing simultaneously<br> 👉 CPU is actively used</p><h3>7. Task.WhenAll — Coordination, Not Magic</h3><pre>var tasks = new[]<br>{<br>    GetUserAsync(),<br>    GetOrdersAsync(),<br>    GetPaymentsAsync()<br>};<br><br>await Task.WhenAll(tasks);</pre><p>What happens:</p><ul><li>all tasks start immediately</li><li>execution overlaps</li><li>completion is awaited collectively</li></ul><p>👉 Total time ≈ longest task<br> 👉 Not sum of tasks</p><p>Important:</p><p>👉 WhenAll does NOT create threads<br> 👉 It only <strong>coordinates tasks</strong></p><h3>8. Thread Starvation — The Silent Killer</h3><p>This is where misunderstandings become production issues.</p><pre>for (int i = 0; i &lt; 100000; i++)<br>{<br>    Task.Run(() =&gt; DoWork());<br>}</pre><p>You just:</p><ul><li>flooded the thread pool</li><li>increased queue time</li><li>slowed everything down</li></ul><p>👉 More tasks ≠ more performance</p><h3>9. Blocking vs Non-Blocking</h3><h4>Blocking</h4><pre>Thread.Sleep(5000);</pre><p>👉 Thread is occupied doing nothing<br> 👉 Cannot be reused</p><h4>Non-blocking</h4><pre>await Task.Delay(5000);</pre><p>👉 Thread is released<br> 👉 Can serve other work</p><h3>10. Continuations and Thread Switching</h3><p>After an await, execution may resume:</p><ul><li>on the same thread</li><li>on another thread from the pool</li></ul><p>👉 You should <strong>never rely on thread identity</strong></p><p>This is why:</p><ul><li>UI frameworks manage synchronization contexts</li><li>server-side code avoids thread assumptions</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a3dc7524bcbd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DRY in Blazor: The Difference Between Clean Code and Chaos]]></title>
            <link>https://wiemksaier.medium.com/dry-in-blazor-the-difference-between-clean-code-and-chaos-5fb45e0d274a?source=rss-a44b0eac5191------2</link>
            <guid isPermaLink="false">https://medium.com/p/5fb45e0d274a</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[blazor]]></category>
            <category><![CDATA[tech]]></category>
            <dc:creator><![CDATA[Wiem Ksaier]]></dc:creator>
            <pubDate>Sat, 18 Apr 2026 21:03:36 GMT</pubDate>
            <atom:updated>2026-04-18T21:03:36.381Z</atom:updated>
            <content:encoded><![CDATA[<p>When you start building with Blazor, everything feels easy.</p><p>You create a page.<br> Then another.<br> Then another…</p><p>And suddenly:</p><ul><li>Same UI appears everywhere</li><li>Same logic is duplicated across components</li><li>Same bugs… reproduced multiple times</li></ul><p>At that point, you’re not building anymore.</p><p>You’re <strong>maintaining repetition</strong>.</p><p>That’s where one principle quietly changes everything:</p><p>=&gt; <strong>DRY — Don’t Repeat Yourself</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*85N10Q1KBXjgdDk8" /><figcaption>Photo by <a href="https://unsplash.com/@kellysikkema?utm_source=medium&amp;utm_medium=referral">Kelly Sikkema</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>What DRY Really Means (Beyond the Definition)</h3><p>DRY is not about writing less code.</p><p>It’s about writing <strong>code that doesn’t betray you later</strong>.</p><p>Because every duplicated line is:</p><ul><li>another place to fix bugs</li><li>another place to forget updates</li><li>another opportunity for inconsistency</li></ul><p>In Blazor, this happens faster than you think.</p><h3>A Classic Blazor Mistake</h3><p>Let’s take something simple: loading data.</p><p>You start with this:</p><pre>@if (IsLoading)<br>{<br>    &lt;p&gt;Loading...&lt;/p&gt;<br>}<br>else<br>{<br>    &lt;p&gt;@Data&lt;/p&gt;<br>}</pre><p>Seems fine.</p><p>Until you repeat it in:</p><ul><li>Dashboard</li><li>Profile page</li><li>Settings</li><li>Reports</li></ul><p>Now you have 6 versions of the same UI.</p><p>Want to improve the design?</p><p>=&gt; Update it 6 times.<br> =&gt; Miss one? Welcome to inconsistency.</p><h3>The DRY Mindset Shift</h3><p>Instead of asking:</p><blockquote><em>“How do I implement this?”</em></blockquote><p>Ask:</p><blockquote><em>“Will I need this again?”</em></blockquote><p>If yes → <strong>extract it immediately</strong></p><h3>Components Are Not Optional — They’re Essential</h3><p>Blazor is built for reuse.</p><p>So use it.</p><p>Instead of repeating:</p><pre>&lt;p&gt;Loading...&lt;/p&gt;</pre><p>Create:</p><pre>&lt;LoadingIndicator /&gt;</pre><p>Now:</p><ul><li>One change → everywhere updated</li><li>Consistent UI</li><li>Cleaner code</li></ul><p>= &gt; That’s DRY done right.</p><h3>Logic Does Not Belong Everywhere</h3><p>Another common pattern:</p><pre>try<br>{<br>    var users = await api.GetUsers();<br>}<br>catch<br>{<br>    await Toast.ShowErrorAsync(&quot;Something went wrong&quot;);<br>}</pre><p>You copy this into:</p><ul><li>Users page</li><li>Admin panel</li><li>Reports</li></ul><p>Now error handling is duplicated everywhere.</p><p>Better approach:</p><ul><li>Centralize API calls</li><li>Centralize error handling</li></ul><p>=&gt; One logic → reused everywhere</p><h3>Build Small Reusable UI Blocks</h3><p>You don’t need big components.</p><p>You need <strong>smart, reusable pieces</strong>.</p><p>Instead of repeating:</p><pre>&lt;div&gt;<br>    &lt;strong&gt;Email:&lt;/strong&gt; @user.Email<br>&lt;/div&gt;</pre><p>Create:</p><pre>&lt;InfoField Label=&quot;Email&quot; Value=&quot;@user.Email&quot; /&gt;</pre><p>Now your UI becomes:</p><ul><li>cleaner</li><li>consistent</li><li>easy to maintain</li></ul><h3>Stop Repeating UI Patterns</h3><p>The real problem is not copying code.</p><p>It’s copying <strong>structures</strong>.</p><p>Example patterns:</p><ul><li>Title + description + icon</li><li>Label + value</li><li>Card layout</li></ul><p>If you see this pattern twice → extract it.</p><p>Because patterns spread faster than code.</p><h3>Templates = Simple but Powerful DRY</h3><p>Sometimes UI is similar… but not identical.</p><p>That’s where <strong>RenderFragment</strong> helps.</p><h4>Without DRY</h4><pre>&lt;div class=&quot;card&quot;&gt;<br>    &lt;h3&gt;Users&lt;/h3&gt;<br>    &lt;p&gt;Total: @UsersCount&lt;/p&gt;<br>&lt;/div&gt;<br><br>&lt;div class=&quot;card&quot;&gt;<br>    &lt;h3&gt;Orders&lt;/h3&gt;<br>    &lt;p&gt;Total: @OrdersCount&lt;/p&gt;<br>&lt;/div&gt;</pre><p>Same structure repeated.</p><h4>With DRY using RenderFragment</h4><p>Create a reusable container:</p><pre>&lt;!-- CardContainer.razor --&gt;<br>&lt;div class=&quot;card&quot;&gt;<br>    @ChildContent<br>&lt;/div&gt;<br><br>@code {<br>    [Parameter]<br>    public RenderFragment ChildContent { get; set; }<br>}</pre><p>Use it:</p><pre>&lt;CardContainer&gt;<br>    &lt;h3&gt;Users&lt;/h3&gt;<br>    &lt;p&gt;Total: @UsersCount&lt;/p&gt;<br>&lt;/CardContainer&gt;<br><br>&lt;CardContainer&gt;<br>    &lt;h3&gt;Orders&lt;/h3&gt;<br>    &lt;p&gt;Total: @OrdersCount&lt;/p&gt;<br>&lt;/CardContainer&gt;</pre><p>=&gt; Same layout<br> =&gt; Different content<br> =&gt; Zero duplication</p><h3>Base Components</h3><p>Sometimes multiple pages share:</p><ul><li>loading state</li><li>initialization logic</li><li>common services</li></ul><p>You can use:</p><pre>@inherits BasePage</pre><p>But be careful:</p><p>=&gt; Use it for shared behavior<br> =&gt; Not for everything</p><p>Too much abstraction can make your code harder to understand.</p><h3>The Trap Most Developers Fall Into</h3><p>Here’s the truth:</p><blockquote><em>Not all duplication is bad.</em></blockquote><p>Trying to remove <strong>every duplication</strong> leads to:</p><ul><li>over-engineering</li><li>unreadable abstractions</li><li>confusion</li></ul><p>=&gt; DRY is about removing <strong>meaningful repetition</strong>, not everything.</p><h3>A Better Way to Think</h3><p>When writing code, ask yourself:</p><ul><li>Will I copy this somewhere else?</li><li>Will I need to update this in multiple places?</li><li>Is this logic reusable?</li></ul><p>If yes → extract it.<br> If no → keep it simple.</p><h3>The Rule That Makes You Better Instantly</h3><blockquote><em>If you write the same thing twice, stop and rethink.</em></blockquote><p>That moment is where good developers become great ones.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5fb45e0d274a" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>