Skip to main content

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

C# 12

FeatureSyntaxUse Case
Primary Constructorsclass Person(string name, int age)DI, immutable types
Collection Expressionsint[] arr = [1, 2, 3];Unified collection syntax
Spread Operator[..arr1, ..arr2]Combine collections
Inline Arrays[InlineArray(10)] struct BufferFixed-size buffers
Alias Any Typeusing Point = (int X, int Y);Complex type aliases
Default Lambda Params(x, y = 10) => x + yOptional lambda parameters

C# 13

FeatureDescriptionBenefit
params Collectionsparams ReadOnlySpan<int>Stack allocation, zero-copy
New Lock TypeLock _lock = new();Type-safe locking
ref struct Interfacesref struct S : IDisposableHigh-perf with interfaces
Partial PropertiesSplit property declarationGenerated code patterns
\e Escape Sequence"\e[31mRed\e[0m"ANSI escape codes

C# 14 (Preview)

FeatureExampleUse Case
Extension MembersExtension propertiesExtend types beyond methods
Null-Conditional Assignmentobj?.Property = valueSafe assignment
field KeywordAccess backing field directlyProperty customization
Partial ConstructorsSplit constructor logicSource 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

MethodPurposeWhen to Use
Monitor.EnterAcquire lockUse lock instead
Monitor.TryEnterTry acquire with timeoutNon-blocking, deadlock detection
Monitor.WaitRelease and waitProducer-consumer, coordination
Monitor.PulseWake one threadSignal single waiter
Monitor.PulseAllWake all threadsSignal 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

  1. Lock Ordering - Always acquire locks in the same order
  2. Timeout - Use Monitor.TryEnter with timeout
  3. Single Lock - Use one lock for related resources
  4. 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

GenerationPurposeCollection Frequency
Gen 0Short-lived objectsVery frequent
Gen 1Buffer between 0 and 2Medium
Gen 2Long-lived objectsRare, expensive
LOHObjects ≥ 85KBWith 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

TypeUse 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

TypeStorageEqualityImmutabilityInheritance
structStack/InlineReference-likeMutableNo
record structStack/InlineValueCan be mutableNo
classHeapReferenceMutableYes
record classHeapValueCan be mutableYes

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>

AspectIEnumerable<T>IQueryable<T>
ExecutionIn-memory (C#)Expression trees (SQL)
Use ForCollectionsDatabases
Where RunsClientServer
// ❌ 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?

lock is syntactic sugar for Monitor.Enter/Exit. Monitor adds TryEnter (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 struct has value equality, ToString, with expressions. struct doesn'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?

IEnumerable is in-memory, IQueryable translates 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.