C#  

10 C# Features You’re Probably Using Wrong

C# is a mature, powerful language—but many of its features are commonly misunderstood or misused, even by experienced developers. These mistakes don’t usually break your code, but they silently hurt performance, readability, and maintainability.

Let’s look at 10 C# features you’re probably using wrong—and how to fix them.

1. async / await — Thinking It Means “Multithreading”

Wrong usage

await DoWorkAsync();

Many developers assume async automatically runs code on another thread. It doesn’t.

Reality

  • async is about non-blocking, not parallelism

  • CPU-bound work still blocks unless explicitly offloaded

Correct approach

await Task.Run(() => DoCpuBoundWork());

Use async for I/O-bound work, not CPU-heavy logic.

2. var — Using It Everywhere (or Nowhere)

Wrong usage

var data = GetData();

This hurts readability when the type isn’t obvious.

Better

List<User> users = GetUsers();

Rule of thumb

  • Use var when the type is obvious

  • Avoid it when it hides intent

3. IEnumerable<T> — Assuming It’s Cached

Wrong usage

var users = GetUsers();
var count = users.Count();
var first = users.First();

If GetUsers() hits a database, this runs multiple queries.

Fix

var users = GetUsers().ToList();

IEnumerable<T> is lazy. Enumerate once when needed.

4. LINQ — Using It for Everything

Wrong usage

items.Select(x => {
    SaveToDatabase(x);
    return x;
}).ToList();

LINQ is for queries, not side effects.

Correct

foreach (var item in items)
{
    SaveToDatabase(item);
}

LINQ should be declarative, not procedural.

5. == vs .Equals() — Assuming They’re the Same

Wrong assumption

obj1 == obj2

Reality

  • == can be overloaded

  • .Equals() checks logical equality

  • ReferenceEquals() checks memory reference

Best practice

obj1.Equals(obj2)

Always know which equality you need.

6. try-catch — Using It for Flow Control

Wrong

try
{
    var value = int.Parse(input);
}
catch
{
    // Ignore
}

Correct

if (int.TryParse(input, out var value))
{
    // Use value
}

Exceptions are expensive. Don’t use them as if statements.

7. Dispose() — Forgetting using

Wrong

var stream = new FileStream(path, FileMode.Open);

Correct

using var stream = new FileStream(path, FileMode.Open);

If it implements IDisposable, dispose it—always.

8. readonly — Confusing It with Immutability

Wrong assumption

readonly List<int> numbers;

The reference is readonly—not the object.

Correct

IReadOnlyList<int> numbers;

readonly ≠ immutable.

9. null — Not Using Nullable Reference Types

Wrong

string name = null;

Correct (C# 8+)

string? name = null;

Enable it:

#nullable enable

Nullable reference types prevent bugs before runtime.

10. Records — Using Them Like Classes

Wrong

public record User
{
    public string Name { get; set; }
}

This defeats the purpose of records.

Correct

public record User(string Name);

Records are for immutable, value-based data.

Final Thoughts

C# gives you powerful tools—but power without understanding leads to subtle bugs and performance issues.

If you:

  • Misuse async

  • Abuse LINQ

  • Ignore disposal

  • Skip nullable references

…your code works, but it doesn’t scale or age well.

Write C# that’s intentional, explicit, and predictable.