By Bogdan Sandu · Updated September 10, 2026
What C# Is
C# is a statically typed, object oriented language that compiles to intermediate language and runs on the .NET runtime. Anders Hejlsberg designed it at Microsoft, and version 1.0 shipped in 2002.
Static typing means the compiler knows the type of every expression before the program runs. Most mistakes surface at build time rather than in production.
The name is written C#, and spelled csharp in URLs, file paths, package names and tags, because the sharp sign has no place in a web address. Both refer to the same language, so a search for either lands you here.
The language has picked up functional features along the way: lambdas, LINQ, records, pattern matching, immutability by default in new types. It is no longer purely object oriented, and modern C# code often reads closer to F# than to Java.
Everything on this page runs on .NET, which is free, open source and cross platform. Windows, Linux and macOS all run the same binaries.
Where C# Runs
- Web APIs and sites: ASP.NET Core, Minimal APIs, Blazor for browser side code compiled to WebAssembly.
- Desktop: WPF, WinForms, WinUI 3 and Avalonia for cross platform desktop.
- Mobile and multi platform: .NET MAUI targets Android, iOS, macOS and Windows from one project.
- Games: Unity uses C# as its scripting language, which makes it one of the most widely used game development languages in the world.
- Cloud and services: Azure Functions, AWS Lambda, worker services, gRPC and background processing.
- Tooling: command line utilities, source generators, Roslyn analyzers and build tasks.
C# Syntax Basics
A C# file is a sequence of type declarations inside namespaces. Since C# 9 a simple program can skip all of that.
The Two Program Shapes
Top level statements let a console app be one line. The compiler generates the class and the Main method for you.
Console.WriteLine("Hello");
The explicit form is still what you get in libraries and larger apps:
namespace MyApp;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello");
}
}
Both compile to the same thing. Pick top level statements for scripts, samples and small tools, and the explicit form when a file holds more than one type.
Statements, Blocks and Semicolons
Every statement ends with a semicolon. Blocks use braces. Whitespace is not significant, so indentation is style rather than syntax.
C# is case sensitive. Total and total are two different identifiers, and the convention is PascalCase for types and public members, camelCase for locals and parameters, and a leading underscore for private fields.
Value Types and Reference Types
This is the distinction that explains most surprising behaviour in C#.
Value types (int, double, bool, char, struct, enum, record struct) hold their data directly. Assigning one copies the data. They usually live on the stack or inline inside another object.
Reference types (class, record, interface, delegate, string, array) hold a reference to data on the heap. Assigning one copies the reference, so both variables now point at the same object.
var a = new List<int> { 1 };
var b = a;
b.Add(2);
Console.WriteLine(a.Count); // 2, same list
int x = 1;
int y = x;
y++;
Console.WriteLine(x); // 1, separate copies
Strings are the exception that trips people up: they are reference types, but they are immutable, so they behave like values. Every operation that looks like a mutation returns a new string.
Nullability in C#
Null reference exceptions were the single most common runtime failure in C# for two decades. C# 8 introduced nullable reference types to move that problem to compile time.
With <Nullable>enable</Nullable> in the project file, string means "never null" and string? means "may be null". The compiler warns when you dereference something it cannot prove is non null.
This is a compile time analysis only. Nothing is checked at runtime, and a value that arrives from JSON, a database or an older library can still be null despite the annotation. Treat the warnings as a strong hint, not a guarantee.
The Null Operators
Prefer is null and is not null over == null. A type can overload ==, and the pattern form always does the plain reference check.
Collections and LINQ
Almost every C# program manipulates sequences, and LINQ is the query language built into the framework for doing it.
Choosing a Collection
List<T> is the default. Reach for Dictionary<TKey, TValue> the moment you find yourself scanning a list to find something by a key, and for HashSet<T> when you only care whether an item is present.
The cost difference is real. Looking up an item in a list of 10,000 entries scans on average 5,000 of them. The same lookup in a dictionary hashes the key once.
Deferred Execution
LINQ operators do not run when you write them. They build a query that runs when you enumerate it.
var query = people.Where(p => p.IsActive); // nothing happened yet
foreach (var p in query) { } // runs here
var list = query.ToList(); // runs again
Two consequences follow. First, enumerating twice does the work twice, so materialise with ToList() when you need the results more than once. Second, if the source changes between building the query and running it, the results reflect the new state.
IEnumerable versus IQueryable
IEnumerable<T> runs in memory using delegates. IQueryable<T> builds an expression tree that a provider such as Entity Framework Core translates into SQL.
The practical rule: call ToList() as late as possible when querying a database, so filtering happens in SQL rather than after pulling every row into memory.
Async and Await Explained
An async method returns a Task that represents work in progress. await suspends the method, returns control to the caller, and resumes when the task completes.
Nothing here creates a thread. For an HTTP call or a database query, the thread is returned to the pool while the network does its work, which is why a small pool can serve thousands of concurrent requests.
The Rules Worth Memorising
- Async all the way down. Calling
.Result or .Wait() on a task blocks the thread and can deadlock in contexts that capture a synchronisation context.
- Return Task, not void. An
async void method cannot be awaited and its exceptions crash the process. Event handlers are the only exception.
- Start before you await. To run two calls concurrently, start both tasks and then await them, or use
Task.WhenAll.
- Pass the CancellationToken. A token that stops at the first layer is a token that does nothing.
- Do not await inside a lock. Use
SemaphoreSlim when the critical section contains asynchronous work.
Awaiting sequentially in a loop is a common cause of slow endpoints. Two calls that each take 200 ms take 400 ms in sequence and roughly 200 ms with Task.WhenAll.
Records, Classes and Structs
C# now offers four ways to declare a type, and the choice is about semantics rather than speed.
Use a class for anything with identity and behaviour: services, entities, controllers, anything you would describe as "the" thing rather than "a" value.
Use a record for data that is defined by its contents: DTOs, API contracts, messages, query results, configuration. You get value equality, a readable ToString, deconstruction and the with expression at no cost.
Use a struct for small immutable values that are created in large numbers, such as coordinates, identifiers and money. The rough guideline is 16 bytes or less, because larger structs are expensive to copy.
Use a record struct when you want both value semantics and value equality.
The with Expression
var original = new Person("Ana", "Pop");
var updated = original with { LastName = "Ionescu" };
This creates a copy with one property changed. The original is untouched, which is what makes records safe to share across threads and cache without defensive copying.
Pattern Matching
Pattern matching started as a nicer if (x is Type t) and has grown into a small sublanguage. It is now the clearest way to express branching logic over shapes of data.
string Describe(object o) => o switch
{
null => "nothing",
int n when n < 0 => "negative number",
int n => $"number {n}",
string { Length: 0 } => "empty string",
string s => $"text of {s.Length}",
int[] { Length: > 0 } arr => $"array starting at {arr[0]}",
_ => "something else"
};
The compiler checks exhaustiveness on switch expressions. If no arm matches at runtime you get a SwitchExpressionException, so a discard arm is usually worth adding.
Common C# Mistakes
- Concatenating strings in a loop. Each
+= allocates a new string. Use StringBuilder past a handful of iterations.
- Comparing floating point numbers with ==. Use a tolerance, or use
decimal for money.
- Catching Exception and swallowing it. An empty catch block hides bugs. Catch the specific type, or let it propagate.
- Using throw ex instead of throw. The first resets the stack trace and destroys the evidence.
- Creating a new HttpClient per request. Sockets leak. Use
IHttpClientFactory or a single shared static instance.
- Modifying a collection while iterating it. Build a second list, or iterate a snapshot with
ToList().
- Calling Count() on an IEnumerable to test emptiness.
Any() stops at the first element.
- Forgetting to dispose. Streams, connections and HTTP responses need
using.
- Overriding Equals without GetHashCode. Dictionary and HashSet lookups then fail silently.
- Public mutable fields. Use properties, so you can add validation later without a breaking change.
C# Naming Conventions
These come from the Microsoft coding conventions, and every mainstream analyzer enforces them. Add an .editorconfig to your solution and the IDE will apply them for you.
How to Practise C#
Reading a reference page is not the same as writing code. A few things that work:
- Install the .NET SDK and run
dotnet new console. The whole loop from empty folder to running program takes under a minute.
- Use
dotnet watch run so the app restarts on every save.
- Write small programs against real data. Parse a CSV, call a public API, count words in a text file.
- Turn on
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> early. The compiler is a better teacher than a tutorial.
- Read the source. The .NET runtime and libraries are on GitHub, and the standard library is written in idiomatic C#.
When something on this page is unclear, the official C# documentation is thorough and kept current with each release. For the other half of the job, our SQL cheat sheet covers the queries your C# code will be sending to the database, and the regex cheat sheet covers the patterns you will pass to System.Text.RegularExpressions.
C# Questions People Actually Ask
What is C# used for?
C# is a statically typed, object oriented language that runs on .NET. It is used for web APIs and sites with ASP.NET Core, desktop apps with WPF and WinUI, cross platform apps with .NET MAUI, games with Unity, cloud functions, background services and command line tools. The same code runs on Windows, Linux and macOS.
What is the difference between a class, a struct and a record in C#?
A class is a reference type with reference equality. A struct is a value type that is copied on assignment and compared field by field. A record is a reference type by default that adds value based equality, a generated ToString, deconstruction and non destructive copying with the with expression. Use record struct when you want value semantics and value equality together.
When should I use async and await in C#?
Use async and await for input and output bound work such as HTTP calls, database queries and file access, so the calling thread is released while the operation is pending. For CPU bound work use Task.Run or the Parallel class instead. Return Task or Task<T> rather than void, except in event handlers, and never block on a task with .Result or .Wait().
What does the question mark mean in C# types?
On a value type, int? means Nullable<int>, a value that can also hold null. On a reference type, string? is a nullable reference annotation that tells the compiler null is expected there. The ?. operator short circuits member access when the left side is null, ?? supplies a fallback value, and ??= assigns only when the target is currently null.
What is LINQ in C#?
LINQ is a set of query operators built into .NET for filtering, projecting, ordering, grouping and aggregating sequences. It comes in method syntax, such as items.Where(x => x.IsActive).Select(x => x.Name), and query syntax that reads like SQL. LINQ queries are lazy, so nothing runs until you enumerate the result or call ToList, ToArray, Count or First.
Which C# version should I target?
The language version follows the target framework. Targeting net8.0 gives you C# 12 with primary constructors and collection expressions, and net9.0 gives you C# 13. For new projects pick the latest long term support release of .NET, and set Nullable and ImplicitUsings to enable in the project file so you start with modern defaults.