C# Null 条件运算符

C# 判断 C# 判断

在 C# 编程中,处理 null 值是一个常见但又容易出错的任务。

传统的 null 检查代码往往冗长且繁琐,而 C# 6.0 引入的 Null 条件运算符?.?[])极大地简化了这一过程。

Null 条件运算符是 C# 6.0 引入的一个语法糖,它允许开发者在访问对象的成员或元素之前,先检查该对象是否为 null。如果对象为 null,则整个表达式的结果为 null,而不会抛出 NullReferenceException

基本语法

  • ?. - 用于访问成员(属性、方法、字段)
  • ?[] - 用于访问索引器或数组元素
语法 说明
a?.b 如果 a 不为 null,则访问 a.b;否则返回 null
a?.Method() 如果 a 不为 null,则调用方法;否则什么都不做
a?[i] 如果 a 不为 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;

如果链式调用中的任何一个环节为 null,整个表达式就会返回 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"

与其它运算符的组合使用

运算符 名称 用途 示例 返回值
? 可空类型声明 声明可以为 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:数据绑定和 UI 开发

实例

// 在 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 = "张三" };

// 对于值类型,返回 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 条件运算符 - 更简洁
protected virtual void OnSomethingHappened()
{
    SomethingHappened?.Invoke(this, EventArgs.Empty);
}

实践练习

练习 1:重构代码

将以下传统 null 检查代码重构为使用 Null 条件运算符:

实例

// 原始代码
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# 判断 C# 判断