Зачем мне использовать оператор ? C#

При изучение C# возник вопрос с ?. Зачем мне его использовать, если я и так могу положить в переменную null. Почему я должен именно использовать string?

string str = null;

var GetStringToUpper = FuncForUpperString(str);
Console.WriteLine(x);

static string FuncForUpperString(string str)
{
    if (str is null)
    {
        return " ";
    }
    return str.ToUpper();
}

Ответы (1 шт):

Автор решения: Uranus

В C# все ссылочные типы по определению nullable. Поэтому формально разработчики должны всегда делать проверку на null, даже если заведомо известно что значение всегда будет присвоено. На практике, разработчики часто опускают проверки на null, особенно в случае когда речь идет о каком-нибудь приватном методе, использование которого легко проследить.

В C# 8 были введены nullable reference types, которые помогут немного навести порядок в этом плане. С практической стороны ничего особо не изменилось - все ссылочные типы по-прежнему технически могут содержать null, несмотря на то, используется или нет оператор ? при объявлении типа. Но этот оператор позволяет явно указывать, может ли ссылочный тип содержать значение null с точки зрения бизнес логики. Если переменная объявлена как string, это означает что допустимо опускать проверки на null (в контексте бизнес логики, разумеется). Если же переменная объявлена как string?, это значит, что она может содержать null. При этом компилятор предупреждает о возможных местах, где может возникнуть null, помогая избежать ошибок. Такой подход делает работу с null более предсказуемой и безопасной.

Как nullable reference types помогают разработчикам

  • Разработчики интерфейсов и библиотек получают возможность явно декларировать свои намерения, когда какой-то параметр или возвращаемое значение может быть null. Это упрощает использование кода другими, делая его более понятным и надежным. В случаях, когда тип может принимать значение null, это указывается прямо в сигнатуре метода, например: string? GetUserEmail().

  • Разработчики, использующие интерфейсы и библиотеки, получают возможность сразу видеть, где нужно добавить проверки на null. Если метод возвращает значение nullable-типа или принимает его, то компилятор напомнит о необходимости добавить проверку, что помогает избежать NullReferenceException.

Как достигнуть большей свободы в декларировании намерений с помощью атрибутов для контроля null

Атрибуты для контроля null помогают разработчикам уточнять, какие значения могут быть null и в каких ситуациях проверка на null является излишней. Ниже приведены некоторые ключевые атрибуты и их применение:

  • [NotNullWhen(true/false)] – Позволяет избежать лишней проверки на null. Этот атрибут полезен для методов, которые возвращают bool и имеют параметр, который может быть null только при определенных условиях.

    public bool TryGetUser(string userId, [NotNullWhen(true)] out User? user) { /* логика метода */ }
    
    // ...
    
    if (TryGetUser("1", out var user)) {
      // здесь проверка if (user == null) больше не нужна
    }
    
  • [MemberNotNull] – Сообщает, что вызов метода гарантирует инициализацию указанных полей или свойств. Это особенно полезно для ленивой инициализации, где значение может быть null до вызова определенного метода, после чего его проверка становится избыточной.

    [MemberNotNull(nameof(Connection))]
    public void Init(IConfiguration configuration) { /* инициализация Connection */ }
    
    // ...
    
    var dbContext = new DbContext();
    dbContext.Init(configuration);
    // здесь проверка if (dbContext.Connection == null) больше не нужна
    
  • [MaybeNull] – Указывает, что метод может вернуть null, даже если возвращаемый тип не nullable. Это позволяет разработчику явно указать, что в определенных случаях значение будет null, а значит, потребуются проверки на стороне вызывающего кода.

    [MaybeNull]
    public Product GetProduct(int id)
    {
        // Возвращаем null, если продукт не найден
    }
    
  • [DisallowNull] – Указывает, что поле или свойство nullable-типа не должно быть null при установке значения, но может быть null после создания объекта. Это особенно полезно для свойств и параметров, которые могут быть null в определенные моменты времени, но не должны быть null во время использования.

    public class User
    {
        [DisallowNull]
        public string? Name { get; set; }
    }
    

Эти атрибуты позволяют точно декларировать поведение с nullable-типами, сокращают необходимость ручных проверок на null и помогают избежать ошибок, связанных с NullReferenceException, делая код более надежным и безопасным.

→ Ссылка