The 20 examples below showcase practical C# development techniques and design patterns that I have used to build maintainable, reusable, and database-driven applications. Each example begins with a real-world development scenario, followed by the C# implementation and a technical explanation of the approach, providing a practical look at how I apply C# to solve common and complex software-development challenges.
The Generic Repository Pattern
Instead of writing the same ‘Save’ or ‘Delete’ code for every single table in my database, I created a master blueprint that handles any data type. It keeps the project clean and cuts development time in half.
public interface IRepository where T : class
{
Task<IEnumerable> GetAllAsync();
Task AddAsync(T entity);
}
public class Repository(DbContext context) : IRepository where T : class
{
private readonly DbSet _dbSet = context.Set();
public async Task<IEnumerable> GetAllAsync() => await _dbSet.ToListAsync();
public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
}
This utilizes Generics () and Entity Framework Core. It abstracts the data access layer, ensuring that the business logic is decoupled from the specific database implementation, promoting the DRY (Don’t Repeat Yourself) principle.
Expression Trees for Dynamic Filtering
I built a tool that lets users build their own reports by picking filters on a screen. The code then ‘translates’ those choices into a high-speed database query on the fly.
ParameterExpression param = Expression.Parameter(typeof(Product), “p”);
MemberExpression member = Expression.Property(param, “Price”);
ConstantExpression constant = Expression.Constant(100.0m);
BinaryExpression body = Expression.GreaterThan(member, constant);
var lambda = Expression.Lambda<Func<Product, bool>>(body, param);
var highValueProducts = products.AsQueryable().Where(lambda).ToList();
This uses Expression<Func<T, bool>>. By manually constructing expression trees, you can build dynamic LINQ queries that are executed server-side (SQL), which is significantly more efficient than pulling all data into memory and filtering it in C#.
Asynchronous Parallel Data Fetching
When a report needs data from three different APIs, I don’t make them wait in line. I launch all three requests at the same time and grab the results the second they are all finished.
var task1 = _apiClient.GetSalesAsync();
var task2 = _apiClient.GetInventoryAsync();
var task3 = _apiClient.GetStaffingAsync();
await Task.WhenAll(task1, task2, task3);
var reportData = new { Sales = task1.Result, Stock = task2.Result, Team = task3.Result };
Employs Task.WhenAll().[1][2][3] This manages multiple asynchronous operations concurrently. It reduces the “Total Time to Load” from the sum of all requests to just the duration of the longest-running request.
Custom Middleware for Execution Timing
I wanted to know exactly which parts of my application were running slowly. I wrote a background ‘watchman’ that times every single request and logs the slowness to a dashboard.
public class TimingMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
await next(context);
Debug.WriteLine($”Request {context.Request.Path} took {sw.ElapsedMilliseconds}ms”);
}
}
Implements IMiddleware in ASP.NET Core. By wrapping the _next() delegate with a Stopwatch, you can capture precise telemetry data for every HTTP request passing through the pipeline.
Strategy Pattern for Dynamic Logic
Business rules change based on the country. Instead of a giant ‘If/Else’ mess, I designed a system where the app ‘swaps out’ the calculation logic automatically based on the region detected.
public interface ITaxStrategy { decimal Calculate(decimal amount); }
public class USTax : ITaxStrategy { public decimal Calculate(decimal a) => a * 0.08m; }
public class EUTax : ITaxStrategy { public decimal Calculate(decimal a) => a * 0.20m; }
public class TaxCalculator(ITaxStrategy strategy)
{
public decimal GetTotal(decimal amount) => amount + strategy.Calculate(amount);
}
This is a Design Pattern implementation. It defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime. It uses an interface (ITaxStrategy) and dependency injection to select the strategy.
Reflection for Automated Validation
I didn’t want to manually check every field for errors. I wrote a system that ‘scans’ my data objects, finds specific ‘tags’ I’ve added, and validates the data automatically based on those tags.
public class RequiredAttribute : Attribute { }
public static bool Validate(object obj)
{
var props = obj.GetType().GetProperties();
foreach (var p in props)
{
if (Attribute.IsDefined(p, typeof(RequiredAttribute)) && p.GetValue(obj) == null)
return false;
}
return true;
}
Uses System.Reflection and Custom Attributes. The code inspects the metadata of a class at runtime to identify properties decorated with specific attributes, allowing for a centralized, decoupled validation engine.
Memory Management with Span
When processing massive text files (like server logs), standard code creates thousands of tiny ‘garbage’ pieces in memory. I used a high-performance technique that ‘points’ to the data without copying it, making the process 5x faster.
ReadOnlySpan logLine = “2023-10-01|ERROR|DatabaseTimeout”;
int firstPipe = logLine.IndexOf(‘|’);
ReadOnlySpan date = logLine.Slice(0, firstPipe);
ReadOnlySpan status = logLine.Slice(firstPipe + 1, 5);
Span and ReadOnlySpan allow for safe, stack-allocated memory access. This avoids “managed heap” allocations and reduces the pressure on the Garbage Collector (GC), critical for high-throughput data processing.
The Circuit Breaker Pattern
If an external data source goes down, I don’t want my app to keep trying and eventually crash. My code ‘trips a breaker’ to stop the requests and tells the user the system is in maintenance mode automatically.
var breaker = Policy.Handle()
.CircuitBreakerAsync(exceptionsAllowedBeforeBreaking: 3, durationOfBreak: TimeSpan.FromMinutes(1));
await breaker.ExecuteAsync(() => _apiClient.GetCriticalDataAsync());
Often implemented via libraries like Polly. It monitors for failures; once a threshold is reached, it enters an “Open” state to prevent further calls, allowing the remote system time to recover.
Thread-Safe Singleton
I needed a single ‘source of truth’ for app settings that every part of the system could access at once without causing data crashes or memory conflicts.
public sealed class GlobalConfig
{
private static readonly Lazy _instance =
new(() => new GlobalConfig());
public static GlobalConfig Instance => _instance.Value;
private GlobalConfig() { }
}
Uses Lazy to implement a thread-safe Singleton pattern. This ensures that only one instance of a class is ever created, even in a multi-threaded environment, while delaying creation until actually needed.
Dependency Injection (Scoped Services)
I designed the app so that parts are ‘plug-and-play.’ If I want to switch from a SQL database to an Oracle database, I only have to change one line of code in the settings, not 100 lines in the app.
builder.Services.AddScoped<IReportingService, PowerBIReportingService>();
// Usage in Controller
public class ReportController(IReportingService reportService) : ControllerBase { … }
Demonstrates IoC (Inversion of Control) containers. By registering services with different lifetimes (Transient, Scoped, Singleton), the app manages object creation and disposal automatically, leading to better testability and modularity.
Event Aggregator for Decoupled Communication
In complex apps, you don’t want every screen ‘talking’ directly to every other screen—it becomes a web of knots. I built a central ‘post office’ where one part of the app drops a message, and whoever is interested picks it up.
public class EventAggregator {
public event Action OnMessagePublished;
public void Publish(string msg) => OnMessagePublished?.Invoke(msg);
}
// Subscriber
_eventAggregator.OnMessagePublished += (msg) => RefreshData(msg);
Implements a simple Pub/Sub (Publisher/Subscriber) pattern. Components subscribe to event types rather than specific instances, reducing hard dependencies and making the system much easier to maintain.
Fluent API Design
I like code that reads like a sentence. I built a data-querying tool that lets developers write code like .FromStore(10).WithSalesAbove(500).SendEmail().
public class ReportBuilder {
public ReportBuilder FromStore(int id) { /…/ return this; }
public ReportBuilder WithSalesAbove(decimal limit) { /…/ return this; }
public void SendEmail() { /…/ }
}
Uses Method Chaining. Each method returns this, allowing for a highly readable Domain-Specific Language (DSL) within C#.
SignalR for Real-Time Reporting
Users hate hitting the ‘Refresh’ button. I built a dashboard that ‘pushes’ new data to the user’s screen the very millisecond it’s updated in the database.
public class ReportHub : Hub {
public async Task SendUpdate(string message) =>
await Clients.All.SendAsync(“ReceiveUpdate”, message);
}
Utilizes WebSockets via SignalR.[4] It establishes a persistent, two-way connection between the server and the client, allowing for “Server Push” notifications instead of traditional “Polling.”
Entity Framework Shadow Properties
I wanted to track when every row in my database was ‘Last Updated,’ but I didn’t want to clutter my C# code with ‘DateUpdated’ fields. I made the database handle it secretly in the background.
modelBuilder.Entity().Property(“LastUpdated”);
// Update value
context.Entry(product).Property(“LastUpdated”).CurrentValue = DateTime.UtcNow;
Shadow Properties are properties that exist in the EF Core model but not in the C# class.[5] They are useful for metadata like LastModified that the business logic doesn’t need to see.
Rate Limiting Middleware
To prevent bots or heavy users from crashing my reporting server, I wrote a ‘gatekeeper’ that only allows a certain number of requests per minute from a single user.
builder.Services.AddRateLimiter(options => options
.AddFixedWindowLimiter(“fixed”, opt => {
opt.PermitLimit = 10;
opt.Window = TimeSpan.FromSeconds(60);
}));
Uses the Fixed Window limiter in the ASP.NET Core middleware pipeline. It monitors client requests and returns 429 Too Many Requests when limits are exceeded.
JSON Source Generators
Traditional apps spend a lot of time ‘figuring out’ how to read JSON data. I used a modern C# feature that ‘pre-learns’ the data format during the build process, making the app much faster when it actually runs.
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(ReportModel))]
internal partial class ReportContext : JsonSerializerContext { }
JsonSourceGenerator (System.Text.Json). Instead of using Reflection at runtime to parse JSON, this generates the serialization code at compile-time, improving startup performance and reducing memory usage.
Operator Overloading for Business Logic
I deal with complex currencies and measurements. I made it so I can literally type CurrencyA + CurrencyB in my code, and the system automatically handles the conversion and math correctly.
public static Money operator +(Money a, Money b) =>
new(a.Amount + b.Amount, a.CurrencyCode);
Defines Operator Overloading. This allows custom classes to use standard mathematical operators, making the code much more intuitive and reducing errors in complex domain logic.
Immutable Records
In financial reporting, you don’t want data changing ‘accidentally’ after it’s been calculated. I used a special type of data object that is ‘locked’ the moment it’s created.
public record FinancialTransaction(decimal Amount, string Currency, DateTime Timestamp);
Uses C# record types. Records provide built-in value-based equality and immutability (by default), which is ideal for Data Transfer Objects (DTOs) and ensuring thread safety.
TPL Dataflow (Pipeline)
I built a data ‘assembly line.’ One part of the code reads a file, the next part encrypts it, and the last part uploads it—all happening simultaneously in a controlled flow.
var transformBlock = new TransformBlock<string, byte[]>(line => Encrypt(line));
var actionBlock = new ActionBlock<byte[]>(data => Upload(data));
transformBlock.LinkTo(actionBlock);
Uses System.Threading.Tasks.Dataflow. This provides a robust actor-based programming model for in-process message passing and pipelining, handling buffering and “back-pressure” automatically.
ConditionalWeakTable for Metadata
I needed to attach ‘extra notes’ to data objects coming from a third-party tool, but I couldn’t edit their code. I created a ‘secret ledger’ that attaches this info without causing memory leaks.
private static readonly ConditionalWeakTable<object, string> _notes = new();
public static void AddNote(object obj, string note) => _notes.AddOrUpdate(obj, note);
ConditionalWeakTable<TKey, TValue>. This allows you to associate data with an object instance without preventing that object from being garbage collected.[6][7] It’s essentially a way to add “hidden properties” to objects you don’t own.