C# is Microsoft's flagship language powering everything from Unity games to enterprise ASP.NET applications. This guide covers all 60 C# and .NET tutorials on TheCodeForge.
The most useful thing to understand about modern .NET is that it is not the .NET Framework people remember. The rewrite that became .NET Core and is now simply .NET is cross-platform, open source, runs in containers without ceremony, and is among the faster mainstream web runtimes. C# itself has quietly absorbed most of what people go to other languages for — pattern matching, records, nullable reference types, async streams, source generators.
What has not changed is where C# developers actually lose time, and it is a short list: async misuse that deadlocks or exhausts the thread pool, Entity Framework doing far more queries than the code suggests, and NullReferenceException in code written before nullable reference types existed. Those three account for most production incidents in .NET services, and all three are covered here.
async does not make code parallel. It frees the calling thread while an I/O operation is outstanding, which is why it raises throughput on a server without adding threads. Two mistakes break that, and both are common enough to have names.
Blocking on async code — .Result, .Wait(), GetAwaiter().GetResult() — deadlocks in any context with a synchronization context, and starves the thread pool everywhere else, because a pool thread sits blocked waiting for work that needs a pool thread. async void is the other: it cannot be awaited, so exceptions escape to the top level and crash the process rather than surfacing at the call site.
.ConfigureAwait(false) to every await. It tells the runtime the continuation does not need the original context, which removes the deadlock risk for whoever calls your library from a UI or legacy ASP.NET application.EF Core's value is that LINQ reads like code and executes as SQL. Its cost is that the translation is invisible, so a small change in a query can move work from the database to your process, or turn one query into a thousand.
Two rules prevent most of it. Know exactly where IQueryable becomes IEnumerable — everything after that point runs in memory, over whatever the database already returned. And load what you will need, explicitly, rather than letting lazy loading issue a query per entity inside a loop.
| Pattern | What actually happens | Prefer |
|---|---|---|
.ToList() then .Where() | Whole table pulled into memory, then filtered | Filter on the IQueryable first, materialise last |
| Navigation property inside a loop | N+1 — one query per iteration | .Include(), or a projection that selects only the columns you need |
.Select() of whole entities for a read-only list | Full entity materialisation plus change tracking | Project to a DTO and add .AsNoTracking() |
.Count() on a loaded collection | Counts in memory over already-fetched rows | .CountAsync() on the queryable — SELECT COUNT(*) |
NullReferenceException is the most common exception in .NET applications, and since C# 8 the compiler can find most instances before you ship. Enabling nullable reference types makes string mean 'never null' and string? mean 'may be null', and the compiler warns wherever your code disagrees with its own declarations.
On an existing codebase, enable it per file or per project and work through the warnings gradually. The temptation to silence them with the null-forgiving ! operator is worth resisting — every ! is a promise to the compiler that you have not verified, and it is exactly where the next NullReferenceException will come from.
The process cannot access the file because it is being used by another process is nearly always your own process holding a handle it never released. A FileStream or StreamWriter that is not disposed keeps the OS handle open until the finaliser eventually runs, which is not a schedule you can rely on.
The using declaration makes this hard to get wrong, and for anything holding an unmanaged resource — files, sockets, database connections — it should be automatic. The one thing that should not be wrapped this way is HttpClient: disposing it per request exhausts sockets, which is why IHttpClientFactory exists.
System.Web or Global.asax it is Framework-era and the advice may not apply.Task is a reference type and allocates. ValueTask avoids that allocation when the result is already available — useful on hot paths that usually complete synchronously, like a cache hit. It has real restrictions: it must not be awaited twice or stored. Use Task by default and reach for ValueTask after measuring.with expression for non-destructive copies, and a readable ToString for nothing. Keep classes for entities with identity and mutable state.Every tutorial starts with a plain-English analogy — then real code, then interview questions.
Browse C# / .NET Tutorials →