C# is a modern, object-oriented programming language developed by Microsoft. It is designed for building a variety of applications that run on the .NET platform, combining the power of C++ with the simplicity of Visual Basic.
C# Anti-Patterns Overview
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.
Using Exception Handling for Control Flow
Exceptions should be used for exceptional conditions, not for normal control flow. Use methods like TryParse
that are specifically designed for validation.
Not Disposing IDisposable Objects
Always dispose IDisposable
objects to release unmanaged resources. The using
statement ensures proper disposal even if exceptions occur.
Excessive Use of Null Checks
Excessive null checks lead to deeply nested code. Use the null conditional operator (?.
) and null coalescing operator (??
) for cleaner code.
Not Using async/await Correctly
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.
Using Public Fields Instead of Properties
Public fields break encapsulation. Use properties to encapsulate fields, allowing for validation, lazy loading, and change notification.
Not Using LINQ When Appropriate
LINQ provides a concise, readable way to query and transform data. Use it for filtering, projecting, and aggregating data.
Using Strings for Sensitive Data
Strings are immutable and can’t be securely cleared from memory. Use SecureString
for sensitive data in memory and proper hashing for storage.
Not Using C# Features Appropriately
Modern C# provides many features like records, pattern matching, and expression-bodied members that can make code more concise and readable.
Using Magic Strings and Numbers
Magic strings and numbers make code hard to maintain and understand. Use constants, enums, or static readonly fields to give meaning to these values.
Not Using Dependency Injection
Hardcoded dependencies make code hard to test and maintain. Use dependency injection to provide dependencies from outside the class.
Not Using IEnumerable<T> for Method Returns
Returning IEnumerable<T>
instead of concrete collection types gives you more flexibility to change the implementation and prevents callers from modifying the collection.
Not Using Proper Exception Handling
Proper exception handling includes catching specific exceptions, logging with context, and either handling the exception appropriately or rethrowing it.
Not Using Object Initializers
Object initializers make code more concise and readable when creating and initializing objects.
Not Using Proper Collection Types
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.
Not Using Nullable Reference Types
In C# 8+, enable nullable reference types to catch potential null reference exceptions at compile time rather than runtime.
Not Using Expression-Bodied Members
Expression-bodied members make simple properties and methods more concise and readable.
Not Using Pattern Matching
Pattern matching (introduced in C# 7 and enhanced in later versions) provides a more concise and powerful way to check types and extract values.
Using Mutable Collections for Public Properties
Exposing mutable collections as public properties breaks encapsulation. Return read-only collections and provide methods to modify the collection.
Not Using Tuple Deconstruction
Tuple deconstruction makes working with tuples more readable and intuitive.