C# Programming: Common Mistakes with Examples

I’m Anni Huang, an AI researcher-in-training currently at ByteDance, specializing in LLM training operations with a coding focus. I bridge the gap between engineering execution and model performance, ensuring the quality, reliability, and timely delivery of large-scale training projects.
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
usingstatements - Handle null values defensively or use nullable reference types
- Use specific exception types and preserve exception chains
- Avoid blocking async calls with
.Resultor.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.




