Skip to main content
C#, despite being a well-designed language with strong typing and modern features, still has common anti-patterns that can lead to bugs, performance issues, and maintenance problems. Here are the most important anti-patterns to avoid when writing C# code.
Exceptions should be used for exceptional conditions, not for normal control flow. Use methods like TryParse that are specifically designed for validation.
Always dispose IDisposable objects to release unmanaged resources. The using statement ensures proper disposal even if exceptions occur.
Excessive null checks lead to deeply nested code. Use the null conditional operator (?.) and null coalescing operator (??) for cleaner code.
Avoid blocking on async code with .Result or .Wait() as it can lead to deadlocks. Also avoid async void except for event handlers as exceptions can’t be caught.
Public fields break encapsulation. Use properties to encapsulate fields, allowing for validation, lazy loading, and change notification.
LINQ provides a concise, readable way to query and transform data. Use it for filtering, projecting, and aggregating data.
Strings are immutable and can’t be securely cleared from memory. Use SecureString for sensitive data in memory and proper hashing for storage.
Modern C# provides many features like records, pattern matching, and expression-bodied members that can make code more concise and readable.
Magic strings and numbers make code hard to maintain and understand. Use constants, enums, or static readonly fields to give meaning to these values.
Hardcoded dependencies make code hard to test and maintain. Use dependency injection to provide dependencies from outside the class.
Returning IEnumerable<T> instead of concrete collection types gives you more flexibility to change the implementation and prevents callers from modifying the collection.
Proper exception handling includes catching specific exceptions, logging with context, and either handling the exception appropriately or rethrowing it.
Object initializers make code more concise and readable when creating and initializing objects.
Choose the appropriate collection type based on how you’ll use it. Use IEnumerable<T> for simple iteration, List<T> when you need to modify the collection, Dictionary<TKey, TValue> for lookups, etc.
In C# 8+, enable nullable reference types to catch potential null reference exceptions at compile time rather than runtime.
Expression-bodied members make simple properties and methods more concise and readable.
Pattern matching (introduced in C# 7 and enhanced in later versions) provides a more concise and powerful way to check types and extract values.
Exposing mutable collections as public properties breaks encapsulation. Return read-only collections and provide methods to modify the collection.
Tuple deconstruction makes working with tuples more readable and intuitive.