C# Cheatsheet - Syntax, LINQ & .NET Core Reference

This reference is for .NET developers building web APIs, services, or desktop apps, focusing on the C# syntax you write every day: classes and interfaces with their access modifiers, LINQ for querying collections instead of hand-written loops, async/await for I/O-bound work, generics and delegates, and the property/event model. Unlike a flat C# feature list, entries are grouped by the language construct and note where C# behavior differs from Java or JS. After reading you should be able to replace a nested loop with a composed LINQ query, set up an async method without deadlocking, and design a type with properties, events and a delegate.

Languages·39 commands·Last updated 2026-07-21
csharp.NETlinq

Typical Use Case

For developers writing C# services and desktop apps. This sheet gathers the syntax that is easiest to forget: type declarations from class/interface/sealed/abstract to record, the three property forms (get/set, init, expression-bodied), the split between delegates (delegate/Action/Func/Predicate) and events, the LINQ chain from filtering to aggregation, and how async/await works with CancellationToken. Use it when you need to decide quickly whether a DTO should be a record or a class, or whether a callback should be a Func or an event.

Classes & Interfaces 7

public class MyClass { }
Class definition
public interface IMyInterface { void Method(); }
Interface definition
public class Derived : Base, IMyInterface { }
Inherit and implement an interface
public abstract class AbstractClass { }
Abstract class
public sealed class SealedClass { }
Sealed class (not inheritable)
public static class Utils { }
Static class
public record Person(string Name, int Age);
Record type (C# 9)

Properties & Methods 6

public int Age { get; set; }
Auto-property
public string Name { get; init; }
Init-only property (C# 9)
public int Age => _age;
Read-only property (expression-bodied)
public void Method() { }
Method definition
public int Add(int a, int b) => a + b;
Expression-bodied method
public event EventHandler MyEvent;
Event definition

LINQ Queries 8

var result = list.Where(x => x > 5);
Filter
var result = list.Select(x => x * 2);
Map
var result = list.OrderBy(x => x.Age);
Sort
var result = list.GroupBy(x => x.Category);
Group
var result = list.First(x => x.Id == 1);
First match
var result = list.Any(x => x > 0);
Any exists
var result = list.Sum(x => x.Price);
Sum
var result = list.Aggregate((a, b) => a + b);
Aggregate

Asynchronous 6

public async Task<int> GetDataAsync() { }
Async method
await Task.Delay(1000);
Async delay
var result = await httpClient.GetStringAsync(url);
Async HTTP request
Task.Run(() => { });
Run on a background thread
await Task.WhenAll(task1, task2);
Await multiple tasks in parallel
CancellationTokenSource cts = new();
Cancellation token

Generics & Delegates 6

public class Stack<T> { }
Generic class
public T Max<T>(T a, T b) where T : IComparable<T> { }
Generic constraint
public delegate void MyDelegate(string msg);
Delegate definition
public event Action<string> OnMessage;
Action delegate
public Func<int, int, int> add = (a, b) => a + b;
Func delegate
public Predicate<int> isPositive = x => x > 0;
Predicate delegate

Collections & Dictionaries 6

List<int> list = new() { 1, 2, 3 };
List
Dictionary<string, int> dict = new();
Dictionary
HashSet<int> set = new() { 1, 2, 3 };
Hash set
Queue<int> queue = new();
Queue
Stack<int> stack = new();
Stack
var tuple = (1, "hello");
Tuple

Parameter matrix

ParameterEffectExample
record / initImmutable data type: positional construction plus init-only properties, ideal for DTOspublic record Person(string Name, int Age);
Func<T, TResult> / Predicate<T>Delegates with a return value / returning bool, for mapping and for conditionsFunc<int, int, int> add = (a, b) => a + b;
CancellationTokenSource cts = new()Cancellation source: pass cts.Token into async methods and interrupt long work with cts.Cancel()CancellationTokenSource cts = new();
async / await / Task.WhenAllNon-blocking concurrency: WhenAll waits on several tasks at once instead of one by oneawait Task.WhenAll(task1, task2);

Common pitfalls

SymptomCompiler refuses to assign a property after the object was constructed.

CauseRecords and init-only properties cannot be reassigned after initialization, by design.

FixSwitch to a class with get/set when you really need mutable state; keep records for DTOs.

SymptomAn async method is missing await, no exception is thrown, and the task fails silently.

CauseAn async method returns a Task; without await the body never runs and exceptions stay unobserved.

FixAwait every async call, or collect the tasks and wait on them with await Task.WhenAll(...).

SymptomAPI calls inside a loop get slower and slower and the UI freezes.

CauseTask.Run or synchronous blocking I/O inside an async method occupies the thread pool.

FixAwait I/O directly (await httpClient.GetStringAsync(url)) and use Task.WhenAll for concurrency.

SymptomA public event is declared but nobody outside the class can raise it.

CauseEvents can only be raised inside the declaring type; outside code may only subscribe with += / -=.

FixIf external code must raise it, use a plain delegate field such as Action<string> plus a Raise method.

Troubleshooting

  1. 1Turn a batch of async calls into real concurrency

    await Task.WhenAll(task1, task2);

    Awaiting one by one is serial; WhenAll runs both at once, so total time is the slower one.

  2. 2Add cancellation to a long-running task

    CancellationTokenSource cts = new();

    Create the source, pass cts.Token into the async method, and let callers exit it via cts.Cancel().

  3. 3Check that a collection has elements before pulling values out

    var result = list.Any(x => x > 0);

    Any is the existence check; combine it with First / Aggregate instead of relying on FirstOrDefault alone.

Command Examples

record plus init for an immutable DTO

public record Person(string Name, int Age); var p = new Person("Ada", 36) with { Age = 37 };

A record gets value equality, ToString and deconstruction for free, and the with expression copies an instance while overriding selected fields — the idiomatic "change one field, keep the rest". With init-only setters, an accidental later mutation fails to compile instead of corrupting the object at runtime.

A LINQ chain: filter, then sort, then sum

var total = orders .Where(x => x.Status == "paid") .OrderByDescending(x => x.Age) .Select(x => x.Price) .Sum();

LINQ methods return IEnumerable, so a chain is lazy: nothing is computed until the terminal operator runs. Putting Where before Sum therefore shrinks the working set, and for IQueryable (EF Core) the same chain order becomes the generated SQL, where a wrong order can cost orders of magnitude.

A generic method with a constraint

public T Max<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) >= 0 ? a : b; }

The where T : IComparable<T> constraint is what makes CompareTo safe inside the body. Without it T only supports trivial checks like equality, and sorting requires a cast and boxing. Constraints are compile-time: a caller passing an unsuitable type fails the build rather than throwing at runtime.

Fan out with Task.WhenAll plus a CancellationTokenSource

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var t1 = httpClient.GetStringAsync(url1, cts.Token); var t2 = httpClient.GetStringAsync(url2, cts.Token); string[] results = await Task.WhenAll(t1, t2);

await Task.WhenAll(...) waits for all tasks at once, which is what actually buys concurrency; using disposes the source. Passing a CancellationToken lets the caller bound the wait so one slow endpoint cannot hang the whole batch — omit it and you are stuck with the default timeout.

Prefer typed collections over untyped containers

List<int> list = new() { 1, 2, 3 }; Dictionary<string, int> dict = new() { ["id"] = 1 }; HashSet<int> seen = new() { 1, 2, 3 }; Queue<int> queue = new(); Stack<int> stack = new();

List<T> and Dictionary<TKey,TValue> beat ArrayList and Hashtable: type errors surface at compile time, and generic collections avoid boxing, often worth one to two orders of magnitude for value types. Hashtable tolerates null keys and compares with Equals while Dictionary forbids both — the usual source of behaviour changes when migrating.

Common Pitfalls

  • Blocking on a bare .Result or .Wait() inside an async/await method deadlocks on a synchronisation context (WinForms, WPF, classic ASP.NET pipeline): the UI thread waits for a result whose continuation needs that same UI thread. Use await everywhere — this is the hardest hang to diagnose.
  • An async Task method that never awaits only produces a compiler warning, and the exception is swallowed silently into a task that completed doing nothing. Do not paper over it with an empty Task.FromResult, and do not wrap a non-awaited call in try/catch expecting it to throw.
  • Every step of a LINQ chain is deferred. Assign the chain to a variable and forget a terminating operation (Count, ToList, foreach) and the query never runs, which shows up as "my code does nothing".
  • var is only safe when the right-hand type is unambiguous: var d = GetData() keeps compiling after a return type changes (IEnumerable to List) while runtime behaviour or performance shifts silently. Avoid var for data structures that evolve across versions.
  • Touching UI controls or non-thread-safe objects inside Task.Run throws. Use Task.Run for CPU-bound work only; for I/O-bound work just await, otherwise you burn thread-pool threads and invite races.
  • With nullable reference types enabled, a missing GetValueOrDefault or explicit null check is only a warning, not an error, and surfaces later as a NullReferenceException. Raise the warning level to error during migration and fix them, or you are just deferring the crash.

Tips

  • LINQ queries are lazily evaluated; they run only when the results are enumerated.
  • async/await is core to asynchronous programming; avoid blocking the UI thread.
  • C# 9 introduced record types, ideal for immutable data models.

FAQ

How do I choose between Func, Action and Predicate?

Decide by return type and parameter list. Func<int, int, int> takes arguments and returns a value, with the trailing type being the return type — here add = (a, b) => a + b is a two-argument function returning one result. Action<string> takes arguments and returns nothing, which is what a notification-style callback needs; public event Action<string> OnMessage on this sheet is exactly that shape. Predicate<int> is the specialised single-argument, bool-returning form — isPositive = x => x > 0 is one — and it exists so a test can be passed around as a value into filtering entry points instead of being hardcoded inline.

What problems do sealed, abstract and static each solve?

All three narrow or fix the boundaries of a type. public sealed class SealedClass blocks inheritance, which suits types whose behaviour is already complete and not meant to be extended — and sealing also lets the compiler optimise member call sites more aggressively. public abstract class AbstractClass is the opposite of static: it can only be derived from, never instantiated, so it carries the contract a set of subtypes must fulfil. public static class Utils forbids both — no inheritance, no instances, only static members — making it the natural home for stateless helpers. The question to ask is whether the type is meant to be implemented by others, frozen against extension, or is purely a namespace of functions.

When is a generic constraint like where T : IComparable<T> actually required?

Constraints tell the compiler what capabilities the type parameter has, and it only accepts your method body once that is guaranteed. public T Max<T>(T a, T b) where T : IComparable<T> is the textbook case: if the body compares a with b, T must implement IComparable<T>, otherwise the compiler has no basis for assuming the comparison is even valid. Leave the constraint off and T is treated as plain object, so comparisons, arithmetic and similar operations all fail to compile. You buy type safety plus the chance for the compiler to emit specialised code for specific value types instead of boxing everything to object.

How do I run several async requests at once instead of awaiting them one by one?

Use await Task.WhenAll(task1, task2). Awaiting one after another means the second request is not even issued until the first comes back, so the round-trip times add up. WhenAll accepts any number of already-started tasks and waits for all of them together, making the total roughly that of the slowest. You can pass more than two, or hand it a collection with Task.WhenAll(tasks). The precondition is that those tasks are running before the call: an async method yields control at its first await, so grab the task variables first and then await them together is what actually produces concurrency.

Which collection should I reach for: Dictionary, HashSet, Queue, Stack or a tuple?

Pick by the operation you need. Dictionary<string, int> holds key-value pairs and is the right structure for lookup, update and de-duplication by key. HashSet<int> keeps only distinct elements and answers membership questions in constant time, which makes it the filter for duplicates. Queue<int> is first-in-first-out, for work processed in arrival order. Stack<int> is last-in-first-out, which is what undo and backtracking need. var tuple = (1, "hello") is not a container at all: it binds a few closely related values together so a method can return two results at once without you defining a type that exists purely to carry them.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us