C# Null 条件运算符
在 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;
}
}
}
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,不会抛出异常
{
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,不会抛出异常
{
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,不会抛出异常
{
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
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"
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);
}
}
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 ? "匿名用户" : "用户不存在");
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();
{
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);
};
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";
{
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;
{
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
// 对于值类型,返回 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;
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;
// 方式 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);
}
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 ?? "无邮箱信息";
}
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 ?? "无商品信息";
}
{
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; }
}
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; }
}
点我分享笔记