C# Null Conditional Operator
In C# programming, handlingnullChecking values is a common but error-prone task.
traditionalnullThe checking code is often verbose and tedious, and the one introduced by C# 6.0Null conditional operator(?.and?[]) greatly simplifies this process.
The Null conditional operator is syntactic sugar introduced by C# 6.0, which allows developers to first check whether the object is null before accessing its members or elements.nullIf the object isnullthe result of the entire expression isnulland will not throwNullReferenceException。
Basic Syntax
?.- Used to access members (properties, methods, fields)?[]- Used to access indexers or array elements
| Syntax | Description |
|---|---|
a?.b |
ifaIf not null, accessa.b; otherwise return null |
a?.Method() |
ifaIf not null, call the method; otherwise, do nothing. |
a?[i] |
ifaIf not null, access the index; otherwise, return null. |
Why do we need the Null conditional operator?
Problems with traditional null checks
Before the Null conditional operator was introduced, developers had to write verbosenullCheck code:
Example
string name = null;
if (person != null)
{
if (person.Address != null)
{
if (person.Address.City != null)
{
name = person.Address.City.Name;
}
}
}
This kind of code is not only verbose, but also error-prone and less readable.
Advantages of the null conditional operator
Using the Null conditional operator, the same logic can be simplified to:
// 使用 Null 条件运算符 - 简洁明了 string name = person?.Address?.City?.Name;
If any step in a chained call is nullnull, the entire expression will returnnull, instead of throwing an exception.
Detailed syntax explanation
1. Member access operator?.
?.The operator is used to safely access the members (properties, methods, fields) of an object.
Access property
Example
{
public string Name { get; set; }
public Address Address { get; set; }
}
class Address
{
public string City { get; set; }
}
// Safely access properties
Person person = null;
string cityName = person?.Address?.City; // Returns null, does not throw an exception
Call method
Example
{
public int Add(int a, int b) => a + b;
}
Calculator calc = null;
int? result = calc?.Add(5, 3); // Returns null, does not throw an exception
access fields
Example
{
public string Value;
}
DataContainer container = null;
string value = container?.Value; // Returns null, does not throw an exception
2. Element access operator?[]
?[]The operator is used to safely access elements of an array or collection.
Accessing array elements
Example
int? firstNumber = numbers?[0]; // Returns null, does not throw an exception
numbers = new int[] { 1, 2, 3 };
firstNumber = numbers?[0]; // return 1
Accessing dictionary elements
Example
string value = dictionary?["key"]; // Returns null, does not throw an exception
dictionary = new Dictionary<string, string> { ["key"] = "value" };
value = dictionary?["key"]; // Return "value"
Combining with other operators
| Operators | Name | Purpose | Example | Return Value |
|---|---|---|---|---|
? |
Nullable type declaration | Declaring a nullable value type. | int? x = null; |
null |
?? |
Null-coalescing operator | If the left side is null, return the right side. | a ?? b |
b |
?. |
Null conditional operator | Safely accessing members | a?.b |
null or the value of b |
?[] |
Null conditional index operator | Safely access arrays or collections | a?[i] |
null or the element |
?: |
Conditional (ternary) operator | Return different values based on condition | a ? b : c |
b or c |
1. With the Null-coalescing operator??Combination
The Null conditional operator is frequently combined with the Null-coalescing operator??Use together to provide a default value:
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); // 输出: 北京
Example
class Address
{
public string City { get; set; }
}
class Person
{
public string Name { get; set; }
public Address Address { get; set; }
public void SayHello()
{
Console.WriteLine($"Hello, I am {Name}");
}
}
class Program
{
static void Main()
{
Person person = null;
// Safely access properties
Console.WriteLine(person?.Name ?? "Unnamed user");
// Safely access nested properties
Console.WriteLine(person?.Address?.City ?? "Unknown city");
// Safely call methods
person?.SayHello();
// Safely access arrays
int[] arr = null;
Console.WriteLine(arr?[0] ?? -1);
}
}
Output:
未命名用户 未知城市 -1
2. With the conditional operator?:Combination
Example
string displayName = person?.Name ?? (person != null ? "Anonymous user" : “User does not exist”);
3. Using in LINQ queries
Example
{
new Person { Name = "Zhang San", Address = new Address { City = "Beijing" } },
new Person { Name = "Li Si", Address = null },
null
};
// Safely access properties that may be null
var cities = people
.Where(p => p?.Address?.City != null)
.Select(p => p.Address.City)
.ToList();
Practical application scenarios
Scenario 1: Data binding and UI development
Example
public string DisplayAddress => $"{Person?.Address?.City ?? "Unknown city"} - {Person?.Address?.Street ?? "Unknown street"}";
// Safely invoke the method
button.Click += (s, e) =>
{
viewModel?.SaveCommand?.Execute(null);
};
Scenario 2: API response handling
Example
{
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; }
}
// Safely handle the API response
ApiResponse<User> response = GetUserFromApi();
string avatarUrl = response?.Data?.Profile?.AvatarUrl ?? "default-avatar.png";
Scenario 3: Configuration reading
Example
{
public DatabaseConfig Database { get; set; }
}
public class DatabaseConfig
{
public string ConnectionString { get; set; }
public int? Timeout { get; set; }
}
// Safely read configuration
AppConfig config = LoadConfiguration();
string connectionString = config?.Database?.ConnectionString ?? "DefaultConnection";
int timeout = config?.Database?.Timeout ?? 30;
Considerations and best practices
1. Return value types
When using the Null conditional operator, you need to pay attention to the return type of the expression:
Example
// For value types, return Nullable<T>
int? nameLength = person?.Name?.Length; // Type is int?
// For reference types, return the type itself
string name = person?.Name; // The type is string
2. Avoid overuse
Although the Null conditional operator is convenient, it should not be overused:
Example
var result = obj1?.Property1?.Method1()?.Property2 ?? defaultValue;
// Recommended - decompose appropriately, improve readability
var temp1 = obj1?.Property1;
var temp2 = temp1?.Method1();
var result = temp2?.Property2 ?? defaultValue;
3. Performance considerations
The Null conditional operator is comparable in performance to explicitnullchecking, but chained calls create temporary variables:
Example
// Method 1: Traditional check
if (person != null && person.Address != null)
{
return person.Address.City;
}
// Method 2: Null conditional operator
return person?.Address?.City;
4. Combining with event invocation
The Null conditional operator is particularly suitable for event invocation:
Example
public event EventHandler SomethingHappened;
protected virtual void OnSomethingHappened()
{
if (SomethingHappened != null)
{
SomethingHappened(this, EventArgs.Empty);
}
}
// Using the null-conditional operator - more concise
protected virtual void OnSomethingHappened()
{
SomethingHappened?.Invoke(this, EventArgs.Empty);
}
Practice exercises
Exercise 1: Refactoring code
The following traditionalnullRefactor the checking code to use the Null conditional operator:
Example
public string GetUserEmail(User user)
{
if (user != null)
{
if (user.Profile != null)
{
if (user.Profile.ContactInfo != null)
{
return user.Profile.ContactInfo.Email;
}
}
}
return "No email information";
}
// Your refactored code
public string GetUserEmail(User user)
{
return user?.Profile?.ContactInfo?.Email ?? "No email information";
}
Exercise 2: Handling collection data
Example
{
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; }
}
// Write amethod,securitygroundGetsubscribe单in theaShang品ofname
public string GetFirstProductName(Order order)
{
return order?.Items?.FirstOrDefault()?.Product?.Name ?? "No product information";
}
Exercise 3: Comprehensive application
Example
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; }
}