C# Programming: Common Mistakes with Examples


Memory and Resource Management Issues
Wrong (Not Disposing Resources):
public void ReadFile(string path)
{
var fileStream = new FileStream(path, FileMode.Open);
var reader = new StreamReader(fileStream);
string content = reader.ReadToEnd();
Console.WriteLine(content);
// Resources never disposed - memory leak!
}
Right (Using 'using' Statements):
public void ReadFile(string path)
{
using var fileStream = new FileStream(path, FileMode.Open);
using var reader = new StreamReader(fileStream);
string content = reader.ReadToEnd();
Console.WriteLine(content);
// Resources automatically disposed
}
// Or traditional using blocks
public void ReadFileTraditional(string path)
{
using (var fileStream = new FileStream(path, FileMode.Open))
using (var reader = new StreamReader(fileStream))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
}
Wrong (Event Handler Memory Leak):
public class Publisher
{
public event EventHandler<string> DataReceived;
protected virtual void OnDataReceived(string data)
{
DataReceived?.Invoke(this, data);
}
}
public class Subscriber
{
public void Subscribe(Publisher pub)
{
pub.DataReceived += HandleData; // Never unsubscribed!
}
private void HandleData(object sender, string data)
{
Console.WriteLine(data);
}
}
Right (Proper Event Cleanup):
public class Subscriber : IDisposable
{
private Publisher _publisher;
public void Subscribe(Publisher pub)
{
_publisher = pub;
_publisher.DataReceived += HandleData;
}
public void Dispose()
{
if (_publisher != null)
{
_publisher.DataReceived -= HandleData;
}
}
private void HandleData(object sender, string data)
{
Console.WriteLine(data);
}
}
Null Reference Issues
Wrong (Not Checking for Null):
public void ProcessUser(User user)
{
var name = user.Name.ToUpper(); // NullReferenceException if user or Name is null
Console.WriteLine($"Processing: {name}");
}
Right (Defensive Programming):
public void ProcessUser(User user)
{
if (user?.Name != null)
{
var name = user.Name.ToUpper();
Console.WriteLine($"Processing: {name}");
}
}
// Or with nullable reference types (C# 8+)
public void ProcessUser(User? user)
{
var name = user?.Name?.ToUpper();
if (name != null)
{
Console.WriteLine($"Processing: {name}");
}
}
Wrong (Returning Null Collections):
public List<string> GetUserRoles(int userId)
{
var user = FindUser(userId);
if (user == null)
return null; // Forces callers to check for null
return user.Roles;
}
Right (Return Empty Collections):
public List<string> GetUserRoles(int userId)
{
var user = FindUser(userId);
if (user == null)
return new List<string>(); // Or Enumerable.Empty<string>().ToList()
return user.Roles ?? new List<string>();
}
// Even better - return IEnumerable
public IEnumerable<string> GetUserRoles(int userId)
{
var user = FindUser(userId);
return user?.Roles ?? Enumerable.Empty<string>();
}
Exception Handling Issues
Wrong (Catching Generic Exception):
try
{
ProcessFile();
ConnectToDatabase();
SendEmail();
}
catch (Exception ex)
{
// Too broad - catches everything including system exceptions
Console.WriteLine("Error occurred");
}
Right (Specific Exception Handling):
try
{
ProcessFile();
ConnectToDatabase();
SendEmail();
}
catch (FileNotFoundException ex)
{
_logger.LogError(ex, "File not found");
}
catch (SqlException ex)
{
_logger.LogError(ex, "Database connection failed");
}
catch (SmtpException ex)
{
_logger.LogError(ex, "Email sending failed");
}
Wrong (Losing Exception Information):
try
{
SomeRiskyOperation();
}
catch (Exception ex)
{
throw new CustomException("Operation failed"); // Original exception lost!
}
Right (Preserving Exception Chain):
try
{
SomeRiskyOperation();
}
catch (Exception ex)
{
throw new CustomException("Operation failed", ex); // Preserves original exception
}
// Or simply rethrowing
try
{
SomeRiskyOperation();
}
catch (SpecificException ex)
{
_logger.LogError(ex, "Specific operation failed");
throw; // Preserves stack trace
}
Async/Await Misuse
Wrong (Blocking Async Calls):
public void ProcessData()
{
var data = GetDataAsync().Result; // Potential deadlock!
Console.WriteLine(data);
}
public async Task<string> GetDataAsync()
{
await Task.Delay(1000);
return "Data";
}
Right (Proper Async Usage):
public async Task ProcessDataAsync()
{
var data = await GetDataAsync();
Console.WriteLine(data);
}
public async Task<string> GetDataAsync()
{
await Task.Delay(1000);
return "Data";
}
Wrong (Not Using ConfigureAwait in Libraries):
public async Task<string> LibraryMethodAsync()
{
var result = await SomeAsyncOperation(); // Captures synchronization context
return result.ToUpper();
}
Right (Using ConfigureAwait(false)):
public async Task<string> LibraryMethodAsync()
{
var result = await SomeAsyncOperation().ConfigureAwait(false);
return result.ToUpper();
}
LINQ and Collections Issues
Wrong (Multiple Enumeration):
public void ProcessItems(IEnumerable<string> items)
{
if (items.Any()) // First enumeration
{
Console.WriteLine($"Count: {items.Count()}"); // Second enumeration
foreach (var item in items) // Third enumeration
{
Console.WriteLine(item);
}
}
}
Right (Single Enumeration):
public void ProcessItems(IEnumerable<string> items)
{
var itemList = items.ToList(); // Single enumeration
if (itemList.Any())
{
Console.WriteLine($"Count: {itemList.Count}");
foreach (var item in itemList)
{
Console.WriteLine(item);
}
}
}
Wrong (Modifying Collection During Iteration):
var numbers = new List<int> { 1, 2, 3, 4, 5 };
foreach (var num in numbers)
{
if (num % 2 == 0)
numbers.Remove(num); // InvalidOperationException!
}
Right (Using Separate Collection or Reverse Iteration):
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var toRemove = numbers.Where(n => n % 2 == 0).ToList();
foreach (var num in toRemove)
{
numbers.Remove(num);
}
// Or using RemoveAll
numbers.RemoveAll(n => n % 2 == 0);
// Or backwards iteration for in-place removal
for (int i = numbers.Count - 1; i >= 0; i--)
{
if (numbers[i] % 2 == 0)
numbers.RemoveAt(i);
}
String Handling Issues
Wrong (String Concatenation in Loops):
public string BuildMessage(List<string> items)
{
string result = "";
foreach (var item in items)
{
result += item + ", "; // Creates new string objects each iteration
}
return result;
}
Right (Using StringBuilder):
public string BuildMessage(List<string> items)
{
var sb = new StringBuilder();
foreach (var item in items)
{
sb.Append(item).Append(", ");
}
return sb.ToString();
}
// Or using string.Join
public string BuildMessage(List<string> items)
{
return string.Join(", ", items);
}
Wrong (Case-Sensitive String Comparison):
public bool IsValidUser(string username)
{
return username == "admin" || username == "user"; // Case sensitive!
}
Right (Culture-Appropriate Comparison):
public bool IsValidUser(string username)
{
return string.Equals(username, "admin", StringComparison.OrdinalIgnoreCase) ||
string.Equals(username, "user", StringComparison.OrdinalIgnoreCase);
}
// Or using HashSet for better performance with many values
private static readonly HashSet<string> ValidUsers = new(StringComparer.OrdinalIgnoreCase)
{
"admin", "user"
};
public bool IsValidUser(string username)
{
return ValidUsers.Contains(username);
}
Object-Oriented Design Issues
Wrong (Exposing Mutable Collections):
public class User
{
public List<string> Roles { get; set; } = new(); // Mutable reference exposed
}
// Usage - external code can modify internal state
var user = new User();
user.Roles.Add("Admin"); // Direct modification of internal collection
Right (Encapsulating Collections):
public class User
{
private readonly List<string> _roles = new();
public IReadOnlyList<string> Roles => _roles.AsReadOnly();
public void AddRole(string role)
{
if (!_roles.Contains(role))
_roles.Add(role);
}
public void RemoveRole(string role)
{
_roles.Remove(role);
}
}
Wrong (Not Implementing IEquatable):
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object obj)
{
return obj is Person person &&
Name == person.Name &&
Age == person.Age;
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Age);
}
// Missing IEquatable<Person> implementation
}
Right (Implementing IEquatable):
public class Person : IEquatable<Person>
{
public string Name { get; set; }
public int Age { get; set; }
public bool Equals(Person other)
{
return other != null &&
Name == other.Name &&
Age == other.Age;
}
public override bool Equals(object obj)
{
return Equals(obj as Person);
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Age);
}
}
Performance Issues
Wrong (Boxing/Unboxing):
public void ProcessValues(List<object> values)
{
foreach (object value in values)
{
if (value is int intValue) // Unboxing
{
Console.WriteLine(intValue * 2);
}
}
}
// Usage causes boxing
var values = new List<object>();
values.Add(42); // Boxing int to object
Right (Using Generics):
public void ProcessValues<T>(List<T> values, Func<T, bool> predicate, Func<T, T> transform)
{
foreach (T value in values)
{
if (predicate(value))
{
Console.WriteLine(transform(value));
}
}
}
// Usage without boxing
var intValues = new List<int> { 42, 24, 13 };
ProcessValues(intValues, x => x > 20, x => x * 2);
Access Modifiers and Encapsulation Issues
Wrong (Public Fields):
public class BankAccount
{
public decimal Balance; // Direct field access - no validation
public string AccountNumber;
}
Right (Properties with Validation):
public class BankAccount
{
private decimal _balance;
public decimal Balance
{
get => _balance;
private set => _balance = value >= 0 ? value : throw new ArgumentException("Balance cannot be negative");
}
public string AccountNumber { get; private set; }
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Deposit amount must be positive");
Balance += amount;
}
public void Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Withdrawal amount must be positive");
if (amount > Balance)
throw new InvalidOperationException("Insufficient funds");
Balance -= amount;
}
}
Key Takeaways
- Always dispose of IDisposable objects using
using
statements - Handle null values defensively or use nullable reference types
- Use specific exception types and preserve exception chains
- Avoid blocking async calls with
.Result
or.Wait()
- Use
ConfigureAwait(false)
in library code - Avoid multiple enumeration of IEnumerable
- Use StringBuilder for string concatenation in loops
- Encapsulate collections and provide controlled access
- Implement IEquatable when overriding Equals
- Use generics to avoid boxing/unboxing
- Prefer properties over public fields
- Clean up event subscriptions to prevent memory leaks
These examples demonstrate proper C# practices that lead to more robust, performant, and maintainable code.
Subscribe to my newsletter
Read articles from Anni Huang directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Anni Huang
Anni Huang
I am Anni HUANG, a software engineer with 3 years of experience in IDE development and Chatbot.