After 20+ years working with C#, I've collected small lessons that made a huge difference in how I write code.
Some of these saved me hours of debugging. Others improved performance or made my code cleaner overnight.
Here are 25 practical C# tips I wish I knew earlier.
Modern C# Features That Improve Your Code
1. Embrace Primary Constructors
I once refactored a class with multiple constructors doing the same thing. It was messy.
Primary constructors simplify everything:
public class Person(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
}
Cleaner, shorter, and easier to maintain.
2. Use Collection Expressions
Initializing collections used to feel verbose.
List<int> numbers = [1, 2, 3, 4, 5];
Less typing, more clarity.
3. Use nameof for Safer Refactoring
A typo once cost me hours.
Console.WriteLine(nameof(Person.Name));
Now the compiler helps you catch mistakes.
4. Use Null-Coalescing for Safer Code
Null reference bugs are painful.
string name = person.Name ?? "Unknown";
Simple and safe.
5. Pattern Matching = Cleaner Logic
Refactoring large conditionals becomes much easier:
if (person is { Age: > 18, Name: not null })
{
Console.WriteLine($"{person.Name} is an adult.");
}
6. Prefer String Interpolation
Readable and clean:
string message = $"Hello, {person.Name}!";
7. Use Tuples Instead of Extra Classes
public (string, int) GetPersonInfo()
{
return ("Alice", 30);
}
Lightweight and efficient.
8. Use Local Functions for Better Structure
void Process()
{
int Add(int a, int b) => a + b;
Console.WriteLine(Add(1, 2));
}
Keeps logic scoped and readable.
9. Use using var Instead of Nested Blocks
using var reader = new StreamReader("file.txt");
Cleaner resource management.
10. Use IAsyncEnumerable for Streaming Data
public async IAsyncEnumerable<int> GetData()
{
for (int i = 0; i < 5; i++)
{
await Task.Delay(100);
yield return i;
}
}
Great for performance.
Code Quality & Clean Design
11. Use Records for Immutable Data
public record Person(string Name, int Age);
No more boilerplate.
12. Nullable Reference Types Save You
string? name = null;
Catch issues at compile time.
13. Switch Expressions > Switch Statements
string result = age switch
{
> 18 => "Adult",
_ => "Minor"
};
14. Use with for Object Updates
var updated = person with { Age = 31 };
15. File-Scoped Namespaces Reduce Noise
namespace MyApp;
Cleaner files instantly.
16. Use required for Safer Models
public class Person
{
public required string Name { get; set; }
}
17. Init-Only Properties for Immutability
public string Name { get; init; }
18. Global Using Saves Repetition
global using System;
19. CallerArgumentExpression for Debugging
public void Validate(bool condition,
[CallerArgumentExpression("condition")] string msg = "")
{
if (!condition) throw new Exception(msg);
}
20. Default Interface Methods
public interface ILogger
{
void Log(string msg) => Console.WriteLine(msg);
}
Performance & Advanced Tips
21. Use Span for High Performance
Span<int> numbers = stackalloc int[10];
22. Use ArrayPool to Reduce GC Pressure
var pool = ArrayPool<int>.Shared;
var arr = pool.Rent(10);
pool.Return(arr);
23. Use ValueTask for Lightweight Async
public ValueTask<int> GetValueAsync() => new(42);
24. ConfigureAwait for Better Performance
await Task.Delay(100).ConfigureAwait(false);
25. Use Stopwatch to Measure Performance
var sw = Stopwatch.StartNew();
// code
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Final Thoughts
These small improvements compound over time.
They made my code:
- Cleaner
- Faster
- Easier to maintain

Top comments (0)