Introduction
Generic programming in C# gives us the power to write flexible, reusable code. But with that power comes some subtle performance pitfalls, especially when checking if a value is null in a generic method. What looks like a simple null check can turn into a silent performance and memory issue. Let’s see why—and how to do it the right way.
The Naive Approach: value == null
A beginner-friendly check might look like this.
public bool IsNull<T>(T value)
{
return value == null;
}
At first glance, this works fine, especially for reference types like string, object, or custom classes. However, the problem appears when T is a value type like int, bool, or struct.
The Boxing Problem
In C#, value types normally live on the stack, which is fast and efficient. But when you use == null on a value type in a generic method, the compiler has to box it. That means:
- The value is wrapped in an object.
- It’s moved from the stack to the heap.
- It now behaves like a reference type (temporarily).
This process takes extra time and uses more memory.
Example
IsNull<int>(0);
This call causes boxing. Even though it returns false (since 0 is not null), the system still has to allocate memory on the heap and clean it up later, wasting performance.
The Right Way: EqualityComparer<T>.Default
C# provides a better option.
public bool IsNull<T>(T value)
{
return EqualityComparer<T>.Default.Equals(value, default(T));
}
Why This Works?
- EqualityComparer<T>. The default gives you the best way to compare values for type T.
- It avoids boxing for value types.
- It correctly checks null for reference types.
- default(T) returns the default value of type.
- null for reference types.
- 0, false, or an empty struct for value types.
Examples
IsNull<string>(null); // true
IsNull<int>(0); // true
IsNull<bool>(false); // true
IsNull<int>(5); // false
// Modern C# Bonus: is null
// From C# 7.0 onward, we can use:
public bool IsNull<T>(T value) where T : class?
{
return value is null;
}
This works great for reference types and nullable value types, and avoids boxing. But for non-nullable value types, a null value will always return false.
Combo Option
If you want to cover both null and default values in one method.
public bool IsNull<T>(T value)
{
return value is null || EqualityComparer<T>.Default.Equals(value, default(T));
}
Difference between value == null VS EqualityComparer<T>.Default.Equals(value, default(T)) VS value is null (C# 7+)
Approach |
Reference Types |
Value Types (Non-Nullable) |
Value Types (Nullable) |
Memory Usage (Value Types) |
value == null |
Fast |
Boxing occurs |
Boxing occurs |
Higher due to boxing |
EqualityComparer<T>.Default.Equals(value, default(T)) |
Good |
Efficient value comparison |
Efficient null check |
Lower, avoids boxing |
value is null (C# 7+) |
Fast |
Always false |
Efficient null check |
Lower, avoids boxing |
Conclusion
Null checks in generic C# code are not as simple as they seem. Using value == null can silently hurt performance due to boxing, especially with value types. Instead, use:
- EqualityComparer<T>.Default.Equals(value, default(T)) for safe and efficient checks across all types.
- is null for clean null checks with reference and nullable types (C# 7+).
By being aware of these small details, you write faster, cleaner, and more memory-efficient code, especially in performance-critical areas.