Оператор null-условия в C#

C# условные операторы

В программировании на C# обработкаnullПроверка значений — распространённая, но подверженная ошибкам задача.

традиционныйnullКод проверки часто многословен и утомителен, а введённый в C# 6.0оператор null-условия(?.и?[]) значительно упрощает этот процесс.

Оператор null-условия — это синтаксический сахар, представленный в C# 6.0, который позволяет разработчикам сначала проверить, равен ли объект null, прежде чем обращаться к его членам или элементам.nullЕсли объектnullрезультатом всего выражения являетсяnullи не выбросит исключениеNullReferenceException。

Базовый синтаксис

  • ?.- Используется для доступа к членам (свойствам, методам, полям)
  • ?[]- Используется для доступа к индексаторам или элементам массива
Синтаксис Описание
a?.b ifaЕсли не null, получить доступa.b; иначе вернуть null
a?.Method() ifaЕсли не null, вызвать метод; иначе ничего не делать.
a?[i] ifaЕсли не null, получить доступ к индексу; иначе вернуть null.

Зачем нам нужен оператор null-условного доступа?

Проблемы с традиционными проверками на null

До появления оператора null-условного доступа разработчикам приходилось писать многословныйnullКод проверки:

Пример

// Традиционный подход — утомительные проверки на null
string name = null;
if (person != null)
{
    if (person.Address != null)
    {
        if (person.Address.City != null)
        {
            name = person.Address.City.Name;
        }
    }
}

Такой код не только многословен, но и подвержен ошибкам и менее удобочитаем.

Преимущества условного оператора null

Используя условный оператор null, ту же логику можно упростить до:

// 使用 Null 条件运算符 - 简洁明了
string name = person?.Address?.City?.Name;

Если какой-либо шаг в цепочке вызовов равен nullnull, всё выражение вернётnull, вместо того чтобы выбросить исключение.


Подробное объяснение синтаксиса

1. Оператор доступа к членам?.

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

Доступ к свойству

Пример

class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

class Address
{
    public string City { get; set; }
}

// Безопасный доступ к свойствам
Person person = null;
string cityName = person?.Address?.City;  // Возвращает null, не выбрасывает исключение

Вызов метода

Пример

class Calculator
{
    public int Add(int a, int b) => a + b;
}

Calculator calc = null;
int? result = calc?.Add(5, 3);  // Возвращает null, не выбрасывает исключение

доступ к полям

Пример

class DataContainer
{
    public string Value;
}

DataContainer container = null;
string value = container?.Value;  // Возвращает null, не выбрасывает исключение

2. Оператор доступа к элементу?[]

?[]Оператор используется для безопасного доступа к элементам массива или коллекции.

Доступ к элементам массива

Пример

int[] numbers = null;
int? firstNumber = numbers?[0];  // Возвращает null, не выбрасывает исключение

numbers = new int[] { 1, 2, 3 };
firstNumber = numbers?[0];  // возвращает 1

Доступ к элементам словаря

Пример

Dictionary<string, string> dictionary = null;
string value = dictionary?["key"];  // Возвращает null, не выбрасывает исключение

dictionary = new Dictionary<string, string> { ["key"] = "value" };
value = dictionary?["key"];  // Возвращает "value"

Комбинирование с другими операторами

Операторы Name Назначение Пример Возвращаемое значение
? Объявление типа, допускающего значение null Объявление типа значения, допускающего значение null. int? x = null; null
?? Оператор объединения с null Если левая часть равна null, возвращается правая часть. a ?? b b
?. Оператор условного null Безопасный доступ к членам a?.b null или значение b
?[] Оператор условного индексирования null Безопасный доступ к массивам или коллекциям a?[i] null или элемент
?: Условный (тернарный) оператор Возврат разных значений в зависимости от условия a ? b : c b или c

1. С оператором null-объединения??Комбинация

Оператор условного null часто комбинируется с оператором null-объединения??Используйте вместе, чтобы указать значение по умолчанию:

Person person = null;

// 提供默认值
string cityName = person?.Address?.City ?? "未知城市";
Console.WriteLine(cityName);  // 输出: 未知城市

person = new Person { Address = new Address { City = "北京" } };
cityName = person?.Address?.City ?? "未知城市";
Console.WriteLine(cityName);  // 输出: 北京

Пример

using System;

class Address
{
    public string City { get; set; }
}

class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }

    public void SayHello()
    {
        Console.WriteLine($"Привет, я {Name}");
    }
}

class Program
{
    static void Main()
    {
        Person person = null;

        // Безопасный доступ к свойствам
        Console.WriteLine(person?.Name ?? "Безымянный пользователь");

        // Безопасный доступ к вложенным свойствам
        Console.WriteLine(person?.Address?.City ?? "Неизвестный город");

        // Безопасный вызов методов
        person?.SayHello();

        // Безопасный доступ к массивам
        int[] arr = null;
        Console.WriteLine(arr?[0] ?? -1);
    }
}

Вывод:

未命名用户
未知城市
-1

2. С условным оператором?:Комбинация

Пример

Person person = null;
string displayName = person?.Name ?? (person != null ? "Анонимный пользователь" : “Пользователь не существует”);

3. Использование в LINQ-запросах

Пример

List<Person> people = new List<Person>
{
    new Person { Name = "Чжан Сань", Address = new Address { City = "Пекин" } },
    new Person { Name = "Ли Сы", Address = null },
    null
};

// Безопасный доступ к свойствам, которые могут быть null
var cities = people
    .Where(p => p?.Address?.City != null)
    .Select(p => p.Address.City)
    .ToList();

Практические сценарии применения

Сценарий 1: Привязка данных и разработка пользовательского интерфейса

Пример

// Безопасная привязка данных в WPF или ASP.NET
public string DisplayAddress => $"{Person?.Address?.City ?? "Неизвестный город"} - {Person?.Address?.Street ?? "Неизвестная улица"}";

// Безопасный вызов метода
button.Click += (s, e) =>
{
    viewModel?.SaveCommand?.Execute(null);
};

Сценарий 2: Обработка ответов API

Пример

public class ApiResponse<T>
{
    public T Data { get; set; }
    public string Error { get; set; }
}

public class User
{
    public string Name { get; set; }
    public Profile Profile { get; set; }
}

public class Profile
{
    public string AvatarUrl { get; set; }
}

// Безопасно обрабатываем ответ API
ApiResponse<User> response = GetUserFromApi();
string avatarUrl = response?.Data?.Profile?.AvatarUrl ?? "default-avatar.png";

Сценарий 3: Чтение конфигурации

Пример

public class AppConfig
{
    public DatabaseConfig Database { get; set; }
}

public class DatabaseConfig
{
    public string ConnectionString { get; set; }
    public int? Timeout { get; set; }
}

// Безопасное чтение конфигурации
AppConfig config = LoadConfiguration();
string connectionString = config?.Database?.ConnectionString ?? "DefaultConnection";
int timeout = config?.Database?.Timeout ?? 30;

Рекомендации и лучшие практики

1. Типы возвращаемых значений

При использовании оператора условного доступа по null необходимо обращать внимание на тип возвращаемого значения выражения:

Пример

Person person = new Person { Name = "Zhang San" };

// Для типов значений возвращайте Nullable<T>
int? nameLength = person?.Name?.Length;  // Тип — int?

// Для ссылочных типов вернуть сам тип
string name = person?.Name;  // Тип — string

2. Избегайте чрезмерного использования

Хотя null-условный оператор удобен, не следует злоупотреблять им:

Пример

// Не рекомендуется — злоупотребление, трудно понять
var result = obj1?.Property1?.Method1()?.Property2 ?? defaultValue;

// Рекомендуется — разумно декомпозировать, повышает читаемость
var temp1 = obj1?.Property1;
var temp2 = temp1?.Method1();
var result = temp2?.Property2 ?? defaultValue;

3. Вопросы производительности

Оператор null-условия по производительности сравним с явнойnullпроверкой, но цепочки вызовов создают временные переменные:

Пример

// Эти два подхода имеют сравнимую производительность
// Метод 1: Традиционная проверка
if (person != null && person.Address != null)
{
    return person.Address.City;
}

// Метод 2: Оператор null-условия
return person?.Address?.City;

4. Совмещение с вызовом событий

Оператор null-условия особенно подходит для вызова событий:

Пример

// Традиционный способ
public event EventHandler SomethingHappened;

protected virtual void OnSomethingHappened()
{
    if (SomethingHappened != null)
    {
        SomethingHappened(this, EventArgs.Empty);
    }
}

// Использование оператора null-conditional — более лаконично
protected virtual void OnSomethingHappened()
{
    SomethingHappened?.Invoke(this, EventArgs.Empty);
}

Практические упражнения

Упражнение 1: Рефакторинг кода

Следующий традиционныйnullПереработайте код проверки, чтобы использовать оператор Null conditional:

Пример

// исходный код
public string GetUserEmail(User user)
{
    if (user != null)
    {
        if (user.Profile != null)
        {
            if (user.Profile.ContactInfo != null)
            {
                return user.Profile.ContactInfo.Email;
            }
        }
    }
    return "Нет информации об электронной почте";
}

// Ваш отрефакторенный код
public string GetUserEmail(User user)
{
    return user?.Profile?.ContactInfo?.Email ?? "Нет информации об электронной почте";
}

Упражнение 2: Обработка данных коллекции

Пример

public class Order
{
    public List<OrderItem> Items { get; set; }
    public Customer Customer { get; set; }
}

public class OrderItem
{
    public Product Product { get; set; }
    public int Quantity { get; set; }
}

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class Customer
{
    public string Name { get; set; }
}

// Напишите метод для получения названия товара из заказа
public string GetFirstProductName(Order order)
{
    return order?.Items?.FirstOrDefault()?.Product?.Name ?? "Нет информации о товаре";
}

Упражнение 3: Комплексное применение

Пример

// Создайте полный пример, демонстрирующий применение условного оператора null в реальном проекте.
public class ShoppingCart
{
    public List<CartItem> Items { get; set; }
    public decimal? CalculateTotal()
    {
        return Items?.Sum(item => item?.Product?.Price * item?.Quantity) ?? 0;
    }
}

public class CartItem
{
    public Product Product { get; set; }
    public int? Quantity { get; set; }
}

C# условные операторы

Другие расширения