Part 1 - Core Foundations for Enterprise C#: OOP & SOLID, Clean Architecture, and Type Semantics

Published: (January 7, 2026 at 06:03 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Core Foundations for Enterprise C#

OOP & SOLID (what scales in real systems)

Key ideas

  • Favor composition over inheritance; program to interfaces for testability.

SOLID principles

  • Single Responsibility → keep classes focused.
  • Open/Closed → extend via interfaces/strategies.
  • Liskov Substitution → subtypes should be substitutable.
  • Interface Segregation → small, purpose‑fit interfaces.
  • Dependency Inversion → depend on abstractions; use DI containers.

Example: Dependency Inversion + Interface Segregation

public interface IEmailSender
{
    Task SendAsync(string to, string subject, string body, CancellationToken ct);
}

public class SmtpEmailSender : IEmailSender
{
    public Task SendAsync(string to, string subject, string body, CancellationToken ct)
    {
        // SMTP implementation...
        return Task.CompletedTask;
    }
}

public class NotificationService
{
    private readonly IEmailSender _sender;
    public NotificationService(IEmailSender sender) => _sender = sender;

    public Task NotifyPasswordResetAsync(string userEmail, CancellationToken ct) =>
        _sender.SendAsync(userEmail, "Reset Your Password", "Link...", ct);
}

Immutability reduces bugs

public record ResetRequest(string Email, DateTime RequestedAt); // immutable DTO

public class User
{
    public string Name { get; init; }   // init‑only setter for object initializers
    public string Email { get; init; }
}

Clean Architecture Boundaries (controllers, application, domain, infra)

Keep layers focused and avoid leaking concerns across boundaries.

+-----------------------------------------------------------+
|               Presentation (Controllers)                 |
|   - map HTTP  application use cases                        |
+-----------------------------------------------------------+
|               Application (Services)                     |
|   - orchestrates use cases, coordinates domain           |
+-----------------------------------------------------------+
|                     Domain (Core)                        |
|   - entities, value objects, policies                     |
+-----------------------------------------------------------+
|               Infrastructure (Adapters)                  |
|   - EF Core, HTTP clients, storage, messaging            |
+-----------------------------------------------------------+

Controller → Service → Domain

[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
    private readonly IUserService _service;
    public UsersController(IUserService service) => _service = service;

    [HttpPost("reset")]
    public async Task Reset([FromBody] ResetRequest req,
                                            CancellationToken ct)
    {
        await _service.RequestPasswordResetAsync(req.Email, ct);
        return Accepted();
    }
}

public interface IUserService
{
    Task RequestPasswordResetAsync(string email, CancellationToken ct);
}

Types: class vs record vs struct (including record struct)

When to choose what

TypeKindTypical use‑caseEquality
classReferencePolymorphism, inheritance, services/entitiesReference
recordReferenceDTOs / value models, need value equalityValue
structValueTiny, allocation‑free modelsValue
record structValueSmall immutable models with record featuresValue

Equality differences

public class UserClass { public string Name { get; init; } }
public record UserRecord(string Name);

var c1 = new UserClass { Name = "A" };
var c2 = new UserClass { Name = "A" };
Console.WriteLine(Equals(c1, c2)); // False (reference equality)

var r1 = new UserRecord("A");
var r2 = new UserRecord("A");
Console.WriteLine(Equals(r1, r2)); // True (value‑based equality)

Records: non‑destructive mutation

public record Person(string Name, int Age);
var p1 = new Person("Alice", 30);
var p2 = p1 with { Age = 31 }; // creates a new instance

Structs: keep tiny and often immutable

public readonly struct Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) => (X, Y) = (x, y);
}

Strings & Collections

(Content to be added – this placeholder marks the next section.)

Immutability, StringBuilder, LINQ Pitfalls

Strings are immutable → avoid heavy concatenation with +

var sb = new StringBuilder();
for (int i = 0; i  n % 2 == 0); // deferred
numbers.Add(2);                              // changes the source before enumeration
var list = query.ToList();                    // materialized; includes the newly added 2

Pitfalls & Tips

  • Be mindful of multiple enumerations – cache the result with ToList() (or ToArray()) when you need to reuse it.
  • LINQ in hot loops can introduce allocations; always measure before deciding to optimise.

Call to Action

If you found this useful, check out Part 2 (Performance & Concurrency Essentials) and Part 3 (Production‑Ready Practices).
Post your own tips or war stories in the comments—let’s build a living checklist for backend engineers.

Series Navigation

Back to Blog

Related posts

Read more »