The choice of collection profoundly affects performance. Dictionary
Concurrency control in C# has several layers. The lock statement is syntactic sugar over Monitor.Enter and Monitor.Exit inside a try/finally, with the locked object ideally a private, static, reference-typed field; locking on this, typeof(...), or strings is a known anti-pattern that can cause deadlocks. Use Monitor directly only when you need TryEnter, Wait, Pulse, or finer control. volatile tells the compiler that a field may be modified by multiple threads, preventing reordering and caching of reads and writes, but it is limited to specific primitive types and reference types. Concurrent collections in System.Collections.Concurrent, including ConcurrentDictionary, ConcurrentQueue, ConcurrentStack, ConcurrentBag, and BlockingCollection, provide thread-safe access without external locking, using lock-free and fine-grained locking internally. For cross-process coordination, Mutex is a single-owner lock that can be named system-wide (often used to ensure only one instance of an app runs), while SemaphoreSlim allows N concurrent holders in-process. AsyncLocal
Resource management follows the IDisposable pattern. A class that owns unmanaged resources should implement IDisposable and follow the dispose pattern: a public Dispose() method that calls Dispose(true) and GC.SuppressFinalize, a protected virtual Dispose(bool disposing) method that frees both managed and unmanaged resources when disposing is true and unmanaged resources only when false, and a finalizer that calls Dispose(false) as a last-resort safety net. Dispose is called deterministically by user code (typically via using), while the finalizer is called by the GC at an indeterminate time and should not allocate or reference other managed objects. Modern code often avoids finalizers by using SafeHandle. The using statement comes in two forms: the classic using (var r = ...) { ... } disposes at the end of the block, while the C# 8 using var r = ... declaration disposes at the end of the enclosing scope, reducing indentation. Throw helpers in .NET 6+ (ArgumentNullException.ThrowIfNull, ArgumentException.ThrowIfNullOrEmpty, ArgumentException.ThrowIfNullOrWhiteSpace) reduce validation boilerplate. Exception.Data is an IDictionary for arbitrary context like request IDs, while InnerException is the conventional wrapped cause; use Data for telemetry and throw new MyException("msg", innerEx) to wrap a cause. Even in managed code, memory leaks arise from references the GC cannot see as unreachable: static fields, unsubscribed event handlers, unbounded caches, and undisposed IDisposable instances. WeakReference
Reflection, logging, and configuration support the runtime diagnostic surface. Reflection in System.Reflection lets you inspect types and invoke members dynamically, which is powerful for DI containers, ORMs, and serializers but is slow and lacks compile-time checks; source generators and expression trees are modern alternatives for performance-critical paths. The System.Text.Json source generator emits serialization code at compile time, eliminating reflection and enabling AOT and trimmed apps. nameof returns a symbol's name as a string with compile-time checking, ideal for argument validation and INotifyPropertyChanged. The CallerMemberName, CallerFilePath, and CallerLineNumber attributes auto-fill parameters with the calling context. Microsoft.Extensions.Logging.ILogger