C# Programming: Common Mistakes with Examples

Anni HuangAnni Huang
7 min read

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.

0
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.