C# Quick Reference Guide
February 17, 2025 · 7 min read
C#, .NET, Quick Reference, Fundamentals, Interview Prep
Introduction
This is the final post in the C# interview series - a quick reference for the questions that come up again and again. Each answer is concise but complete enough to satisfy an interviewer.
Language Fundamentals
What's the difference between const and readonly?
public class Constants
{
public const int MaxItems = 100; // Compile-time, inlined
public readonly int MinItems; // Runtime, set in constructor
public static readonly int Default = ComputeDefault(); // Runtime, once
public Constants(int min)
{
MinItems = min; // Can assign in constructor
// MaxItems = 50; // Error! const can't be changed
}
}
Gotcha: const values are baked into calling assemblies at compile time. If you change a const in a library and don't recompile consumers, they'll still use the old value.
Explain boxing and unboxing
int value = 42;
object boxed = value; // Boxing: copy to heap, wrap in object
int unboxed = (int)boxed; // Unboxing: copy back to stack
// Performance cost in hot paths
var list = new ArrayList();
for (int i = 0; i < 1000000; i++)
list.Add(i); // Boxing every int!
// Fix: Use generic collections
var list = new List<int>(); // No boxing
What are delegates and events?
// Delegate: Type-safe function pointer
public delegate int Calculator(int a, int b);
Calculator add = (a, b) => a + b;
int result = add(5, 3); // 8
// Built-in delegates
Func<int, int, int> multiply = (a, b) => a * b; // Returns value
Action<string> log = msg => Console.WriteLine(msg); // No return
Predicate<int> isEven = n => n % 2 == 0; // Returns bool
// Event: Encapsulated delegate
public class Button
{
public event EventHandler? Clicked;
public void OnClick()
{
Clicked?.Invoke(this, EventArgs.Empty);
}
}
// Gotcha: Event memory leaks
button.Clicked += HandleClick; // If you don't -= you have a leak
Async and Threading
What's the difference between Task.Run and Task.Factory.StartNew?
// Task.Run - Simple, sane defaults
await Task.Run(() => DoWork());
// Task.Factory.StartNew - More control, dangerous defaults
await Task.Factory.StartNew(
() => DoWork(),
CancellationToken.None,
TaskCreationOptions.DenyChildAttach, // Important!
TaskScheduler.Default);
// Gotcha: StartNew with async delegate
await Task.Factory.StartNew(async () => await DoWorkAsync());
// Returns Task<Task>! Not what you want.
// Fix: Unwrap or just use Task.Run
await Task.Run(() => DoWorkAsync()); // Handles this correctly
Explain CancellationToken
public async Task ProcessAsync(CancellationToken cancellationToken)
{
foreach (var item in items)
{
// Check for cancellation
cancellationToken.ThrowIfCancellationRequested();
// Pass token to async operations
await ProcessItemAsync(item, cancellationToken);
}
}
// Creating tokens
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(30)); // Timeout
try
{
await ProcessAsync(cts.Token);
}
catch (OperationCanceledException)
{
// Handle cancellation
}
Memory and Performance
Explain IDisposable and using
public class ResourceHolder : IDisposable
{
private bool _disposed;
private readonly Stream _stream;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
_stream?.Dispose(); // Dispose managed resources
}
// Free unmanaged resources here
_disposed = true;
}
~ResourceHolder() // Finalizer - rarely needed
{
Dispose(false);
}
}
// Modern using declaration (C# 8+)
using var file = File.OpenRead("test.txt");
// Disposed at end of scope
// IAsyncDisposable for async cleanup
await using var resource = new AsyncResource();
What is the Large Object Heap (LOH)?
Objects larger than 85KB (on 64-bit) are allocated on the LOH instead of the regular heap. The LOH:
- Is not compacted (by default) - causes fragmentation
- Collected only during Gen 2 collections
- Can be configured:
GCSettings.LargeObjectHeapCompactionMode
Tip: Avoid creating large temporary objects. Use ArrayPool<T> for buffers.
Explain Garbage Collection generations
- Gen 0: Short-lived objects, collected frequently
- Gen 1: Buffer between short and long-lived
- Gen 2: Long-lived objects, collected infrequently
Objects that survive collection are promoted to the next generation. Most objects die young (Gen 0), so this strategy is efficient.
Collections and LINQ
When to use which collection?
| Collection | Best For | Avoid When |
|---|---|---|
List<T> | Sequential access, index lookup | Frequent insertions in middle |
Dictionary<K,V> | Key-based lookup | Ordered iteration |
HashSet<T> | Unique items, contains checks | Need ordering |
Queue<T> | FIFO processing | Random access |
Stack<T> | LIFO processing | Random access |
LinkedList<T> | Frequent insert/remove | Index-based access |
LINQ deferred vs immediate execution
// Deferred - query built, not executed
var query = users.Where(u => u.IsActive); // No execution yet
// Immediate - executes now
var list = users.Where(u => u.IsActive).ToList(); // Executes
var count = users.Count(u => u.IsActive); // Executes
var first = users.First(u => u.IsActive); // Executes
// Gotcha: Multiple enumeration
var query = GetExpensiveQuery();
var count = query.Count(); // Executes
var list = query.ToList(); // Executes AGAIN
// Fix: Materialize once
var list = query.ToList();
var count = list.Count; // Uses cached list
ASP.NET Core
Explain the middleware pipeline
app.UseExceptionHandler("/error"); // Runs last (wraps everything)
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication(); // Must be before Authorization
app.UseAuthorization();
app.MapControllers();
// Custom middleware
app.Use(async (context, next) =>
{
// Before
await next(); // Call next middleware
// After
});
Order matters! Authentication must come before Authorization, etc.
Explain dependency injection lifetimes
services.AddTransient<IService, Service>(); // New instance every injection
services.AddScoped<IService, Service>(); // One per request
services.AddSingleton<IService, Service>(); // One for app lifetime
Gotcha: Don't inject scoped services into singletons (captive dependency).
What's the difference between AddMvc, AddControllers, and AddControllersWithViews?
services.AddControllers(); // API only, no views
services.AddControllersWithViews(); // MVC with Razor views
services.AddRazorPages(); // Razor Pages only
services.AddMvc(); // Everything (legacy)
Design and Architecture
Explain microservices vs monolith
| Microservices | Monolith |
|---|---|
| Independent deployment | Single deployment |
| Technology diversity | Consistent stack |
| Complex operations | Simpler operations |
| Network latency | In-process calls |
| Scale independently | Scale entire app |
Start with monolith, extract services when you have clear boundaries and team scale.
What is CQRS?
Command Query Responsibility Segregation: separate read and write models.
// Command - changes state
public record CreateOrderCommand(int CustomerId, List<OrderItem> Items);
// Query - reads state
public record GetOrderQuery(int OrderId);
// Separate handlers, potentially separate databases
public class CreateOrderHandler : ICommandHandler<CreateOrderCommand> { }
public class GetOrderHandler : IQueryHandler<GetOrderQuery, OrderDto> { }
Useful when read and write patterns differ significantly.
Explain Event Sourcing briefly
Instead of storing current state, store the sequence of events that led to it:
// Instead of: UPDATE Account SET Balance = 150
// Store events:
AccountCreated { InitialBalance = 0 }
MoneyDeposited { Amount = 200 }
MoneyWithdrawn { Amount = 50 }
// Current state derived by replaying events
Benefits: Complete audit trail, temporal queries, event-driven architecture. Costs: Complexity, eventual consistency, storage growth.
Quick Syntax Reference
Null handling
// Null-conditional
var name = user?.Profile?.Name;
// Null-coalescing
var name = user?.Name ?? "Anonymous";
// Null-coalescing assignment
list ??= new List<string>();
// Null-forgiving (careful!)
string name = GetPossiblyNull()!; // Trust me, it's not null
Pattern matching summary
// Type pattern
if (obj is string s) { }
// Property pattern
if (user is { IsActive: true, Role: "Admin" }) { }
// Switch expression
var discount = customer switch
{
{ IsPremium: true } => 0.2m,
{ YearsActive: > 5 } => 0.1m,
_ => 0m
};
// List pattern (C# 11)
if (arr is [1, 2, .. var rest]) { }
Series Conclusion
This series covered the C# and .NET topics that come up most in interviews. The key to doing well:
- Know the fundamentals cold - value/reference types, async/await, LINQ
- Understand the why - Don't just memorize, know when to use what
- Have real examples - "At my job, we had this issue with..."
- Know the gotchas - Interviewers love edge cases
- Stay current - Records, pattern matching, .NET 8 features
Good luck with your interviews!
Sources and Further Reading
For continued learning, I recommend:
- Real .NET Interview Questions from 2024/2025
- Top .NET Interview Questions - DEV Community
- Advanced C# Interview Questions - Turing
- .NET Interview Questions - InterviewBit
Part 7 of the C# Interview Prep series.