C# 12-14 Quick Reference Guide
A comprehensive quick reference for modern C# features and advanced concepts. Perfect for interview prep and quick lookups.
📑 Table of Contents
- C# Version Features
- Threading & Synchronization
- Memory & Performance
- Type System
- Async Programming
- LINQ
- Common Interview Questions
C# Version Features
C# 12
| Feature | Syntax | Use Case |
|---|---|---|
| Primary Constructors | class Person(string name, int age) | DI, immutable types |
| Collection Expressions | int[] arr = [1, 2, 3]; | Unified collection syntax |
| Spread Operator | [..arr1, ..arr2] | Combine collections |
| Inline Arrays | [InlineArray(10)] struct Buffer | Fixed-size buffers |
| Alias Any Type | using Point = (int X, int Y); | Complex type aliases |
| Default Lambda Params | (x, y = 10) => x + y | Optional lambda parameters |
C# 13
| Feature | Description | Benefit |
|---|---|---|
| params Collections | params ReadOnlySpan<int> | Stack allocation, zero-copy |
| New Lock Type | Lock _lock = new(); | Type-safe locking |
| ref struct Interfaces | ref struct S : IDisposable | High-perf with interfaces |
| Partial Properties | Split property declaration | Generated code patterns |
| \e Escape Sequence | "\e[31mRed\e[0m" | ANSI escape codes |
C# 14 (Preview)
| Feature | Example | Use Case |
|---|---|---|
| Extension Members | Extension properties | Extend types beyond methods |
| Null-Conditional Assignment | obj?.Property = value | Safe assignment |
| field Keyword | Access backing field directly | Property customization |
| Partial Constructors | Split constructor logic | Source generators |
Threading & Synchronization
lock vs Monitor
// lock - simple cases
lock (_lockObj) { /* critical section */ }
// Compiles to:
bool lockTaken = false;
try {
Monitor.Enter(_lockObj, ref lockTaken);
/* critical section */
} finally {
if (lockTaken) Monitor.Exit(_lockObj);
}
// Monitor - advanced scenarios
if (Monitor.TryEnter(_lockObj, timeout)) {
try { /* work */ }
finally { Monitor.Exit(_lockObj); }
}
Monitor Methods
| Method | Purpose | When to Use |
|---|---|---|
Monitor.Enter | Acquire lock | Use lock instead |
Monitor.TryEnter | Try acquire with timeout | Non-blocking, deadlock detection |
Monitor.Wait | Release and wait | Producer-consumer, coordination |
Monitor.Pulse | Wake one thread | Signal single waiter |
Monitor.PulseAll | Wake all threads | Signal all waiters |
Producer-Consumer Pattern
// Producer
lock (_lock) {
while (_queue.Count >= _maxSize)
Monitor.Wait(_lock);
_queue.Enqueue(item);
Monitor.Pulse(_lock);
}
// Consumer
lock (_lock) {
while (_queue.Count == 0)
Monitor.Wait(_lock);
item = _queue.Dequeue();
Monitor.Pulse(_lock);
}
Deadlock Prevention
- Lock Ordering - Always acquire locks in the same order
- Timeout - Use
Monitor.TryEnterwith timeout - Single Lock - Use one lock for related resources
- Minimize Scope - Hold locks for minimal time
What NOT to Lock On
❌ lock (this) // External code can lock
❌ lock ("string") // Strings are interned
❌ lock (typeof(MyClass)) // Type objects shared
❌ lock (42) // Value types box
✅ lock (new object()) // Private lock object
✅ lock (new Lock()) // C# 13 Lock type
Memory & Performance
GC Generations
| Generation | Purpose | Collection Frequency |
|---|---|---|
| Gen 0 | Short-lived objects | Very frequent |
| Gen 1 | Buffer between 0 and 2 | Medium |
| Gen 2 | Long-lived objects | Rare, expensive |
| LOH | Objects ≥ 85KB | With Gen 2 |
Span<T> vs Memory<T>
// Span\<T\> - stack-only, zero allocation
Span<int> span = stackalloc int[100];
span.Slice(10, 20); // No allocation
// Memory\<T\> - heap-safe, can store in fields
Memory<int> memory = new int[100];
await ProcessAsync(memory); // OK in async
// ❌ Can't use Span in async
// Span<int> span = ...;
// await SomeAsync(); // ERROR
When to Use Each
| Type | Use When |
|---|---|
| Span<T> | Sync code, hot paths, parsing |
| Memory<T> | Async code, stored in fields |
| ArrayPool<T> | Reusable buffers |
| List<T> | Dynamic collections |
Task vs ValueTask
// Task - reference type, always allocates
async Task<int> GetDataAsync() => await _cache.GetAsync(key);
// ValueTask - struct, avoid allocation if sync
async ValueTask<int> GetDataAsync() {
if (_cache.TryGet(key, out var value))
return value; // No allocation!
return await _database.GetAsync(key);
}
// ⚠️ ValueTask rules:
// - Await only once
// - Don't use .Result before completion
// - Don't convert to Task unless needed
Type System
Value vs Reference Types
// Value Types (stack/inline)
int, struct, enum, record struct
// Reference Types (heap)
class, record class, string, arrays, delegates
// Boxing example
int value = 42; // Stack
object boxed = value; // Heap allocation!
int unboxed = (int)boxed;
struct, record struct, class, record class
| Type | Storage | Equality | Immutability | Inheritance |
|---|---|---|---|---|
| struct | Stack/Inline | Reference-like | Mutable | No |
| record struct | Stack/Inline | Value | Can be mutable | No |
| class | Heap | Reference | Mutable | Yes |
| record class | Heap | Value | Can be mutable | Yes |
When to Use struct
✅ Size ≤ 16 bytes
✅ Logically a single value
✅ Immutable
✅ Short-lived
✅ Won't be boxed frequently
❌ Large data structures
❌ Needs inheritance
❌ Long-lived objects
ref, in, out
// ref - pass by reference, read/write
void Increment(ref int value) { value++; }
// in - pass by reference, read-only (optimization)
void Process(in LargeStruct data) { /* read only */ }
// out - pass by reference, must be initialized
bool TryParse(string s, out int result) { /* ... */ }
Async Programming
ConfigureAwait
// Library code - use false to avoid capturing context
await SomeAsync().ConfigureAwait(false);
// UI code - use true (default) to return to UI thread
await LoadDataAsync(); // Returns to UI thread
Cancellation
public async Task ProcessAsync(CancellationToken ct) {
// Check before expensive operation
ct.ThrowIfCancellationRequested();
// Pass to async methods
await LongOperationAsync(ct);
// Check in loops
while (!ct.IsCancellationRequested) {
await ProcessItemAsync(ct);
}
}
// Create linked token
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
existingToken, timeoutToken);
Async Streams
async IAsyncEnumerable<int> GetNumbersAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
for (int i = 0; i < 100; i++) {
ct.ThrowIfCancellationRequested();
await Task.Delay(100, ct);
yield return i;
}
}
// Consume
await foreach (var num in GetNumbersAsync(ct)) {
Console.WriteLine(num);
}
LINQ
IEnumerable<T> vs IQueryable<T>
| Aspect | IEnumerable<T> | IQueryable<T> |
|---|---|---|
| Execution | In-memory (C#) | Expression trees (SQL) |
| Use For | Collections | Databases |
| Where Runs | Client | Server |
// ❌ BAD - Loads all users, filters in memory
var users = context.Users.AsEnumerable()
.Where(u => u.IsActive);
// ✅ GOOD - Filters in database
var users = context.Users.Where(u => u.IsActive);
Deferred vs Immediate Execution
// Deferred - executes when enumerated
var query = numbers.Where(n => n > 5);
// Immediate - executes now
var list = numbers.Where(n => n > 5).ToList();
var count = numbers.Count(n => n > 5);
var first = numbers.First(n => n > 5);
Performance Tips
// ✅ Use Any() not Count() > 0
if (users.Any()) { /* ... */ }
// ✅ Filter before projecting
users.Where(u => u.IsActive).Select(u => new UserDto());
// ✅ Materialize once if enumerating multiple times
var list = query.ToList();
Common Interview Questions
Threading
Q: lock vs Monitor?
lockis syntactic sugar forMonitor.Enter/Exit. Monitor addsTryEnter(timeout),Wait(release & wait),Pulse/PulseAll(wake threads).
Q: Prevent deadlocks?
Lock ordering, timeouts with
TryEnter, minimize lock scope, avoid nested locks.
Q: Why not lock on this/string/typeof?
External code can lock on them, causing unintended coordination and deadlocks.
Memory
Q: GC generations?
Gen 0 (short-lived, frequent), Gen 1 (buffer), Gen 2 (long-lived, expensive). LOH for objects ≥ 85KB.
Q: When use ValueTask?
High-throughput scenarios where results often cached/immediate. Avoid allocation for sync completions.
Q: Why can't Span be in async?
Span is stack-only. Async methods use state machines on heap. Use Memory<T> for async.
Language
Q: Primary constructor params - fields or properties?
Neither - captured parameters (like closures). Create properties explicitly if needed.
Q: struct vs record struct?
record structhas value equality, ToString, with expressions.structdoesn't.
Q: What is boxing?
Converting value type to object/interface. Creates heap copy. Bad for performance.
Async
Q: ConfigureAwait(false)?
Avoids capturing synchronization context. Use in libraries. UI code needs context (default true).
Q: IEnumerable vs IQueryable?
IEnumerableis in-memory,IQueryabletranslates to SQL/remote queries.
Q: Deferred execution?
LINQ queries execute when enumerated, not when defined. See current collection state.
Quick Syntax Reference
Delegates & Events
// Action (void return)
Action<int> action = x => Console.WriteLine(x);
// Func (returns value)
Func<int, int, int> add = (x, y) => x + y;
// Event
public event EventHandler<MyEventArgs> MyEvent;
MyEvent?.Invoke(this, args);
Patterns
// Pattern matching
if (obj is string { Length: > 0 } str) { }
// Switch expression
var result = value switch {
0 => "zero",
> 0 => "positive",
_ => "negative"
};
Disposal
// IDisposable
using var resource = new Resource();
// IAsyncDisposable
await using var resource = new AsyncResource();
Cheat Sheet Summary
C# 12: Primary constructors, Collection expressions [...], Spread ..
C# 13: params Span, New Lock type, ref struct interfaces
C# 14: Extension properties, field keyword, null-conditional =
Threading: Use lock usually, Monitor for timeout/Wait/Pulse
Memory: Span (sync), Memory (async), ArrayPool (reuse)
Async: ValueTask (hot path), ConfigureAwait(false) (libs)
Types: struct (small, value), record (value equality)
LINQ: IQueryable (DB), IEnumerable (memory), ToList() (materialize)
For detailed explanations, see individual concept files in each phase's folder.