Table of Contents

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 not pattern (available in C# 9.0+, with ! alternative in earlier versions)

If your project targets an earlier C# version, this diagnostic is automatically suppressed.

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.