AL0016: Combine declaration with subsequent null-check
Intent
Encourages combining a local variable declaration with an immediate null check into a single pattern match expression. This refactoring reduces line count, improves readability, and expresses intent more directly: "declare x only if it's not null, otherwise take this action."
Starting with C# 7.0, pattern matching makes this possible using the pattern: if (initializer is not { } variable). This idiom eliminates unnecessary temporary variables and makes the null-check intent explicit in the declaration itself.
When it triggers
AL0016 detects a specific code pattern: a local variable declaration immediately followed (on the next line) by a null check on that variable. The following patterns trigger the diagnostic:
// Pattern 1: Declaration followed by is null check with return
var x = M();
if (x is null) return;
// Pattern 2: == null check
var y = GetValue();
if (y == null) return;
// Pattern 3: Followed by throw
var result = Compute();
if (result is null) throw new InvalidOperationException();
// Pattern 4: Followed by continue
var item = GetItem();
if (item is null) continue;
// Pattern 5: Followed by break
var data = FetchData();
if (data is null) break;
All of these can be combined into a single pattern match expression using the is not { } pattern.
Examples - Before and After
Example 1: Return statement
// Before
public string Format(IData? data)
{
var value = data?.GetValue();
if (value is null) return "";
return $"Value: {value}";
}
// After
public string Format(IData? data)
{
if (data?.GetValue() is not { } value) return "";
return $"Value: {value}";
}
The combined pattern makes it clear that value is only used if it's not null.
Example 2: Throw statement
// Before
public void ProcessItem(Item? item)
{
var context = item?.Context;
if (context is null) throw new InvalidOperationException("No context");
Context = context;
}
// After
public void ProcessItem(Item? item)
{
if (item?.Context is not { } context) throw new InvalidOperationException("No context");
Context = context;
}
By combining the declaration and check, the guard condition is immediately visible.
Example 3: Continue in loop
// Before
foreach (var item in items)
{
var processed = Process(item);
if (processed is null) continue;
Add(processed);
}
// After
foreach (var item in items)
{
if (Process(item) is not { } processed) continue;
Add(processed);
}
This pattern is especially useful in loops where skipping null results is a common pattern.
Example 4: Block with multiple statements
// Before
var config = LoadConfig();
if (config is null)
{
throw new FileNotFoundException("config.json not found");
}
// After
if (LoadConfig() is not { } config)
{
throw new FileNotFoundException("config.json not found");
}
Even with block statements, the combined form clearly expresses the intent while reducing nesting.
Non-Examples (does NOT trigger)
The following patterns do not trigger AL0016 because they don't match the refactoring criteria:
// Multiple variables in declaration
var x = M(), y = N();
if (x is null) return;
// Statement between declaration and null-check
var x = M();
y = ProcessX(x);
if (x is null) return;
// If statement has else clause
var x = M();
if (x is null)
return;
else
Console.WriteLine(x);
// Checking different variable
var x = M();
var y = N();
if (y is null) return;
// Complex null check (not simple is/== null)
var x = M();
if (x?.Value is null) return;
// Conditional null check
var x = M();
if (someCondition && x is null) return;
// Code targeting C# < 7.0
// (pattern variables not supported in older language versions)
These patterns are left unchanged because:
- Multiple declarations in one statement have unclear variable scope after refactoring
- Intervening statements mean the declaration and check aren't directly consecutive
- Else clauses require different handling
- Complex null checks don't fit the simple pattern
- Older language versions don't support pattern variables
Language Version Requirement
This refactoring requires C# 7.0 or later due to the use of:
- Pattern variables (
is not { } variable) - The
notpattern (available in C# 9.0+, with!alternative in earlier versions)
If your project targets an earlier C# version, this diagnostic is automatically suppressed.
Related Patterns
The is not { } pattern is part of C#'s pattern matching family:
is { }- matches non-null values (C# 8.0+)is null- matches null values (C# 7.0+)is not null- matches non-null values (C# 9.0+)
For maximum clarity and modern style, is not { } is often preferred because it emphasizes that you're checking for a non-null object.