Оператор null-условия в 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Код проверки:
Пример
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. Оператор доступа к членам?.
?.Оператор используется для безопасного доступа к членам (свойствам, методам, полям) объекта.
Доступ к свойству
Пример
{
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, не выбрасывает исключение
Вызов метода
Пример
{
public int Add(int a, int b) => a + b;
}
Calculator calc = null;
int? result = calc?.Add(5, 3); // Возвращает null, не выбрасывает исключение
доступ к полям
Пример
{
public string Value;
}
DataContainer container = null;
string value = container?.Value; // Возвращает null, не выбрасывает исключение
2. Оператор доступа к элементу?[]
?[]Оператор используется для безопасного доступа к элементам массива или коллекции.
Доступ к элементам массива
Пример
int? firstNumber = numbers?[0]; // Возвращает null, не выбрасывает исключение
numbers = new int[] { 1, 2, 3 };
firstNumber = numbers?[0]; // возвращает 1
Доступ к элементам словаря
Пример
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); // 输出: 北京
Пример
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. С условным оператором?:Комбинация
Пример
string displayName = person?.Name ?? (person != null ? "Анонимный пользователь" : “Пользователь не существует”);
3. Использование в LINQ-запросах
Пример
{
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: Привязка данных и разработка пользовательского интерфейса
Пример
public string DisplayAddress => $"{Person?.Address?.City ?? "Неизвестный город"} - {Person?.Address?.Street ?? "Неизвестная улица"}";
// Безопасный вызов метода
button.Click += (s, e) =>
{
viewModel?.SaveCommand?.Execute(null);
};
Сценарий 2: Обработка ответов API
Пример
{
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 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 необходимо обращать внимание на тип возвращаемого значения выражения:
Пример
// Для типов значений возвращайте 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 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: Комплексное применение
Пример
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; }
}