← Назад ко всем статьям

Оптимизируем Nullable Annotations с [NotNullWhen(true)]

Опубликовано

Если вы включали nullable annotations в C#, то наверняка видели warnings CS8602 и CS8603.

Некоторые из этих warnings полезны: они помогают находить possible null-reference errors. Но в более сложных methods compiler не всегда понимает ваши custom checks. Он все еще считает, что values могут быть null, даже если вы уже их проверили.

Attributes из System.Diagnostics.CodeAnalysis позволяют явно описать method contracts и убрать этот noise.

Простой Try* example

Before: шумные warnings

bool TryGetDomain(string? url, out string? domain)
{
    if (url is null || !url.Contains("://"))
    {
        domain = null;
        return false;
    }

    domain = url.Split("://")[1];
    return true; // domain is not null here
}

if (TryGetDomain(link, out var d))
{
    Console.WriteLine(d.Length); // CS8602: Possible null reference
}

Compiler видит, что domain declared nullable, и assumes, что он может быть null, даже когда method returns true.

After: explicit contract, no noise

using System.Diagnostics.CodeAnalysis;

bool TryGetDomain(
    string? url,
    [NotNullWhen(true)] out string? domain)
{
    if (url is null || !url.Contains("://"))
    {
        domain = null;
        return false; // null is allowed when we return false
    }

    domain = url.Split("://")[1];
    return true; // null is impossible when we return true
}

if (TryGetDomain(link, out var d))
{
    Console.WriteLine(d.Length); // No warning
}

[NotNullWhen(true)] говорит analyzer: “Если method returns true, out parameter никогда не null.”

Результат - cleaner call sites с меньшим количеством manual checks.

Зачем этим заниматься?

  1. Меньше null checks. Code остается leaner.

  2. Contracts в signature. Behavior проще понять при чтении и code review.

  3. Более точный static analysis. Compiler может фокусироваться на настоящих issues.

Другие attributes, которые стоит знать

  • [MaybeNullWhen(true)] указывает, что out или ref parameter может быть null, когда method returns true.

  • [NotNullIfNotNull("param")] указывает, что return value не будет null, если указанный input parameter не null.

  • [MemberNotNull("Field")] указывает, что method гарантирует initialization field или property до выхода.

  • [DoesNotReturnIf(true)] указывает, что method never returns, когда annotated Boolean parameter равен true, обычно потому что он throws exception.

Как начать

В .NET 5 и новее эти attributes встроены.

Добавляйте их к methods, behavior которых соответствует описанным contracts. Это дает compiler больше context, не меняя runtime behavior.