C# is null: Pattern Matching for Null Checks
c# is null: Learn how to use the `is null` pattern in C# for clear, reliable null checks, and see how it compares to other null-checking approaches.
When you need to check whether a reference is null, the c# is null pattern is often the clearest choice. It is part of C# pattern matching and offers a direct, non-overloadable way to test for null. This article explains how it works, how it differs from == null, and when to use it.
The is null Pattern in C#
The is null pattern is a form of pattern matching introduced in C# 7.0. It checks whether a reference or nullable value is null. The syntax is straightforward:
if (value is null) { // handle null }
This expression returns true when value is null, and false otherwise. It works with reference types, nullable value types, and even unconstrained generic types. The is null pattern is not an operator; it is a constant pattern that matches the null literal. Because it is part of pattern matching, it cannot be overloaded, which is a key difference from the equality operator.
How is null Differs from == null
The most common alternative is value == null. For most types, both expressions produce the same result. However, the equality operator can be overloaded. If a type defines its own == operator, the behavior of value == null depends on that overload. The is null pattern always performs a direct reference or nullable value check, ignoring any custom equality logic.
Consider a custom type that overloads == to treat an empty string as null:
public class MyString { public string Value { get; set; } public static bool operator ==(MyString left, MyString right) { if (left is null && right is null) return true; if (left is null || right is null) return false; return left.Value == right.Value; } public static bool operator !=(MyString left, MyString right) => !(left == right); }
Now, if you have a MyString instance with Value set to "", myString == null might return false (since the overload compares Value), but myString is null will always return false because the instance itself is not null. Conversely, if the overload were designed to treat an empty string as null, == could return true while is null returns false. This is a subtle but important distinction.
For nullable value types, is null and == null behave identically because nullable types do not overload the equality operator in a way that changes the null check. The compiler translates both to a check on the HasValue property.
The following table summarizes the key differences:
| Aspect | == null | is null |
|---|---|---|
| Overloadable | Yes | No |
| Nullable value types | Works | Works |
| Reference types | Works | Works |
| Compiler flow analysis | Limited | Enhanced |
| Performance | May call overloaded operator | Direct comparison |
Using is not null for Positive Checks
The negation of is null is is not null, also introduced in C# 9.0. It provides a clear way to check that a value is not null:
if (value is not null) { // value is not null }
This is often more readable than value != null, especially when the variable name is long or the condition is part of a larger expression. The is not null pattern also respects the same rules as is null: it cannot be overloaded and always performs a direct null check.
One advantage of using is not null is that it works well with pattern matching in switch expressions and property patterns. For example:
string result = value switch { null => "null", not null => "not null" };
This makes the intent explicit and avoids the need for a separate if statement.
Null-Conditional and Null-Coalescing Operators
While is null and is not null are used for conditional checks, C# also provides operators that handle null propagation and fallback values. The null-conditional operator ?. short-circuits the expression if the left operand is null:
int? length = text?.Length;
If text is null, length becomes null instead of throwing a NullReferenceException. The null-coalescing operator ?? provides a default value when the left operand is null:
string name = input ?? "unknown";
These operators are not replacements for is null; they solve different problems. Use is null when you need to branch on the null state, and use ?. and ?? when you want to safely access members or supply defaults without writing explicit conditionals.
Performance and Compiler Behavior
From a performance perspective, is null and == null compile to nearly identical IL for most types. The JIT can optimize both to a simple comparison against the null reference. The main difference is that is null cannot be overloaded, so the compiler can generate a direct comparison without calling a user-defined operator. In contrast, == null may require a virtual call if the type overloads the operator.
For nullable value types, both compile to a check on the HasValue property, which is also efficient. The compiler also uses flow analysis to track null state when you use is null or is not null. This is particularly important with nullable reference types enabled. For example:
string? maybeNull = GetValue(); if (maybeNull is not null) { Console.WriteLine(maybeNull.Length); // no warning }
The compiler recognizes that maybeNull is not null after the is not null check, so it does not emit a nullable warning. This flow analysis works with is null as well, allowing you to return early or throw exceptions without additional casts.
Common Mistakes and Edge Cases
One common mistake is using is null on a value type that is not nullable. The compiler will issue an error because the pattern can never match. For example:
int number = 5; if (number is null) // error CS0037: Cannot convert null to 'int' because it is a non-nullable value type
This is a compile-time error, which is helpful.
Another edge case involves unconstrained generic types. If you have a generic method where T can be a reference type or a value type, is null works correctly:
public static bool IsNull<T>(T value) { return value is null; }
For a value type like int, value is null returns false because the value is boxed and the boxed object is not null. For a nullable value type, it returns true if the nullable has no value. This is consistent with the behavior of == null for generics.
A subtle issue arises when you use is null with a nullable value type that has a value. The pattern matches only if HasValue is false. If you need to check whether a nullable has a value, you can use is not null or check HasValue directly.
Finally, be aware that is null does not invoke any user-defined conversion or equality logic. If you have a type that implements an implicit conversion to another type, the pattern matching does not apply conversions. This keeps the check predictable and avoids surprising behavior.