0Pricing
C# Academy · Урок

Аннотации: ?, !, MaybeNull и NotNull

Используйте ? для типов, допускающих null, ! для подавления предупреждений о null, а также такие атрибуты, как MaybeNull и NotNullWhen, для точного анализа потока.

«Аннотации: ?, !, MaybeNull и NotNull» — бесплатный урок C# Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

За пределами ? и !: атрибуты допустимости null

Базовая аннотация ? и проверка на null подходят для большинства случаев, но для некоторых шаблонов нужны более выразительные аннотации. Пространство имён System.Diagnostics.CodeAnalysis предоставляет атрибуты, которые дают компилятору более глубокое представление о потоке значений null.

MaybeNull и NotNull

[MaybeNull] сообщает компилятору, что тип возвращаемого значения, не допускающий null, на практике всё же может вернуть null (например, в обобщённом методе). [NotNull] гарантирует, что параметр, допускающий null, не будет равен null после вызова.

using System.Diagnostics.CodeAnalysis;

// [MaybeNull]: return might be null even though T is non-nullable
[return: MaybeNull]
public T Find<T>(int id)
{
    // Returns default(T) which is null for reference types
    return _cache.TryGetValue(id, out var val) ? val : default!;
}

// [NotNull]: after this call, output is guaranteed non-null
public static void EnsureNotNull<T>(
    [NotNull] ref T? value,
    T defaultValue) where T : class
{
    value ??= defaultValue;
}

NotNullWhen: условие отсутствия null

[NotNullWhen(true)] сообщает компилятору, что выходной параметр не равен null, когда метод возвращает true. Так размечается стандартный шаблон TryParse.

using System.Diagnostics.CodeAnalysis;

public static bool TryParseEmail(
    string? input,
    [NotNullWhen(true)] out string? email)
{
    if (input?.Contains('@') == true)
    {
        email = input.Trim().ToLower();
        return true;
    }
    email = null;
    return false;
}

// Usage — no warning after true check:
if (TryParseEmail(raw, out var email))
    Console.WriteLine(email.Length); // safe — email is non-null here

MaybeNullWhen: условие наличия null

[MaybeNullWhen(false)] работает наоборот: выходной параметр может быть равен null, когда метод возвращает false. Этот атрибут используется в шаблонах словаря с TryGetValue.

// This is how Dictionary<K,V>.TryGetValue is annotated:
public bool TryGetValue(
    TKey key,
    [MaybeNullWhen(false)] out TValue value)
{ ... }

// Usage:
if (!dict.TryGetValue("key", out var value))
    return; // early return — value is null in this branch

Console.WriteLine(value.Length); // safe after the guard

AllowNull и DisallowNull

[AllowNull] для свойства, не допускающего null, позволяет вызывающему коду передать ему null (например, если метод задания значения заменяет null на пустую строку). [DisallowNull] запрещает null для типа, допускающего null.

public class Config
{
    private string _name = "";

    // Allow setting null (setter converts null -> empty)
    [AllowNull]
    public string Name
    {
        get => _name;
        set => _name = value ?? "";
    }

    // Getter always returns non-null: fine
    // Setter accepts null: [AllowNull] tells compiler that's OK
}

NotNullIfNotNull: передача допустимости null

[NotNullIfNotNull(paramName)] означает: если параметр X не равен null, возвращаемое значение также не равно null. Это полезно для функций преобразования.

[return: NotNullIfNotNull(nameof(value))]
public static string? Normalize(string? value)
{
    return value?.Trim().ToLower();
}

// Usage:
string  name = "  Alice  ";
string  norm1 = Normalize(name)!; // guaranteed non-null
string? raw   = GetRaw();
string? norm2 = Normalize(raw);   // still nullable (raw might be null)

DoesNotReturn

[DoesNotReturn] помечает метод, который всегда выбрасывает исключение. Компилятор знает, что код после вызова недостижим, и подавляет лишние предупреждения о null.

using System.Diagnostics.CodeAnalysis;

[DoesNotReturn]
public static void ThrowNotFound(int id)
    => throw new KeyNotFoundException($"Entity {id} not found");

// Usage — no null warning after the call:
var order = _db.Orders.Find(id);
if (order is null) ThrowNotFound(id);

Console.WriteLine(order.Id); // no CS8602 — compiler knows ThrowNotFound threw

MemberNotNull: постусловие для полей

[MemberNotNull] сообщает компилятору, что метод гарантирует отсутствие null в определённых полях после своего возврата. Это полезно для вспомогательных методов отложенной инициализации.

public class DataLoader
{
    private string? _data;

    [MemberNotNull(nameof(_data))]
    private void EnsureLoaded()
    {
        if (_data is null)
            _data = LoadFromFile();
    }

    public string GetData()
    {
        EnsureLoaded();
        return _data; // no CS8603 — compiler knows _data is set
    }
}

Комбинирование аннотаций

Атрибуты можно комбинировать для создания точных контрактов API. Ниже показан текучий вспомогательный метод-защита, объединяющий несколько атрибутов.

public static class Guard
{
    [return: NotNull]
    public static T NotNull<T>(
        [NotNull][AllowNull] T? value,
        [CallerArgumentExpression(nameof(value))] string? name = null)
        where T : class
    {
        ArgumentNullException.ThrowIfNull(value, name);
        return value;
    }
}

// Usage:
var product = Guard.NotNull(await _repo.FindAsync(id));
Console.WriteLine(product.Name); // no warning

Операторы для работы с null: ?. ?? ??=

Операторы проверки на null (?.), объединения с null (??) и присваивания с объединением по null (??=) позволяют писать лаконичный безопасный код без громоздких проверок if.

string? name = GetName();

// ?. safe navigation
int? len = name?.Length;
string? upper = name?.ToUpper().Trim();

// ?? default value
string display = name ?? "Anonymous";

// ??= lazy init
name ??= "Default";

// Chaining
string result = user?.Profile?.DisplayName ?? user?.Name ?? "Guest";

Быстрая проверка

Что атрибут [NotNullWhen(true)] на выходном параметре сообщает компилятору?

Итоги: аннотации допустимости null

Основные выводы:

  • [NotNullWhen(true)]: шаблон TryParse — значение не равно null при возврате true
  • [MaybeNull]: тип, не допускающий null, всё же может вернуть null (значения по умолчанию в обобщённых типах)
  • [DoesNotReturn]: метод всегда выбрасывает исключение — код после него недостижим
  • [MemberNotNull]: метод гарантирует, что поля заданы после его возврата
  • ?., ??, ??=: лаконичная безопасная навигация и значения по умолчанию при работе с null

Часто задаваемые вопросы

Урок «Аннотации: ?, !, MaybeNull и NotNull» бесплатный?

Да — полный текст урока «Аннотации: ?, !, MaybeNull и NotNull» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Аннотации: ?, !, MaybeNull и NotNull»?

Используйте ? для типов, допускающих null, ! для подавления предупреждений о null, а также такие атрибуты, как MaybeNull и NotNullWhen, для точного анализа потока. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Аннотации: ?, !, MaybeNull и NotNull»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Включение и понимание NRT
  2. Аннотации: ?, !, MaybeNull и NotNull
  3. Операторы условного объединения и объединения с null
  4. Перенос кодовой базы на NRT
← Назад к C# Academy