Part 1 - Core Foundations for Enterprise C#: OOP & SOLID, Clean Architecture, and Type Semantics
Source: Dev.to
Core Foundations for Enterprise C#
- OOP & SOLID (what scales in real systems)
- Clean Architecture Boundaries (controllers, application, domain, infra)
- Types: class vs record vs struct (including record struct)
- Strings & Collections (immutability, StringBuilder, LINQ pitfalls)
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
| Type | Kind | Typical use‑case | Equality |
|---|---|---|---|
| class | Reference | Polymorphism, inheritance, services/entities | Reference |
| record | Reference | DTOs / value models, need value equality | Value |
| struct | Value | Tiny, allocation‑free models | Value |
| record struct | Value | Small immutable models with record features | Value |
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()(orToArray()) 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.