NullReferenceException の回避とコードの簡略化
オブジェクトが null である可能性がある場合、そのオブジェクトのプロパティやメソッドにアクセスしようとすると、System.NullReferenceException が発生し、アプリケーションがクラッシュします。
これを防ぐために、従来はアクセス前に if (obj != null) というチェックを行う必要がありました。しかし、階層の深いオブジェクトにアクセスする場合など、このチェック記述はコードを冗長にし、可読性を低下させる要因となります。
C# 6.0 で導入された「null条件演算子(?.)」を使用すると、このnullチェックとメンバーアクセスを単一の式で、安全かつ簡潔に記述できます。
null条件演算子(?.)の基本
null条件演算子 ?. は、左側のオペランド(オブジェクト)が null でない場合にのみ、右側のメンバー(プロパティやメソッド)にアクセスします。もし左側が null であった場合、アクセスを行わずに即座に null を返します。
構文: オブジェクト?.メンバー
コード例:リストからのデータ取得
ユーザー情報を管理するリストから、特定の条件(ID)でユーザーを検索し、その名前を取得するケースを考えます。検索結果が見つからない(nullになる)可能性がある場面です。
using System;
using System.Collections.Generic;
using System.Linq;
public class UserProfile
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
public class NullConditionalExample
{
public static void Main()
{
var users = new List<UserProfile>
{
new UserProfile { Id = 101, UserName = "Alice", Email = "alice@example.com" },
new UserProfile { Id = 102, UserName = "Bob", Email = "bob@example.com" }
};
// 存在しないID (999) を検索 -> 結果は null になる
var targetUser = users.FirstOrDefault(u => u.Id == 999);
// --- 従来の方法 ---
string nameLegacy = null;
if (targetUser != null)
{
nameLegacy = targetUser.UserName;
}
Console.WriteLine($"従来の方法: [{nameLegacy}]");
// --- null条件演算子 (?.) を使用 ---
// targetUser が null なので、.UserName にはアクセスせず null が返される
string nameModern = targetUser?.UserName;
Console.WriteLine($"null条件演算子: [{nameModern}]");
}
}
出力結果:
従来の方法: []
null条件演算子: []
targetUser が null の場合、targetUser.UserName と書くと例外が発生しますが、targetUser?.UserName と記述することで、安全に null が格納されます。
null合体演算子(??)との組み合わせ
null条件演算子は、結果が null になる可能性があるため、「値が取得できなかった場合のデフォルト値」を設定する null合体演算子 (??) と組み合わせて使用されることが非常に多いです。
// ユーザーが見つかればその名前を、見つからなければ "Unknown User" を返す
string displayName = targetUser?.UserName ?? "Unknown User";
Console.WriteLine($"表示名: {displayName}");
出力結果:
表示名: Unknown User
インデクサ(配列・リスト)へのアクセス (?[ ])
null条件演算子は、配列やリストのインデクサに対しても使用できます。この場合、?[index] という形式になります。
コレクション自体が null の場合に、インデックスアクセスによる例外を防ぐことができます(ただし、インデックスの範囲外アクセスを防ぐものではありません)。
using System;
public class ArrayAccessExample
{
public static void Main()
{
string[] tags = null; // 配列自体が null
// tags が null なので、[0] にはアクセスせず null を返す
string firstTag = tags?[0];
if (firstTag == null)
{
Console.WriteLine("タグ配列は null でした。");
}
}
}
出力結果:
タグ配列は null でした。
戻り値の型に関する注意点(値型の場合)
アクセス対象のプロパティが int や DateTime などの「値型」である場合、null条件演算子の戻り値は「null許容値型(int? など)」になります。
null が返ってくる可能性がある以上、元の型が int であっても、結果は null を許容する必要があるためです。
public class Product
{
public int StockCount { get; set; } // int型
}
// ...
Product item = null;
// item?.StockCount の戻り値は int ではなく int? (Nullable<int>) になる
int? count = item?.StockCount;
// もし int として扱いたい場合は ?? でデフォルト値を指定する
int strictCount = item?.StockCount ?? 0;
まとめ
null条件演算子(?.)は、オブジェクトが null かもしれない状況でのメンバーアクセスを安全かつ簡潔にします。
obj?.Member:objがnullならnullを返し、そうでなければMemberの値を返します。list?[index]: コレクションがnullの場合のインデックスアクセスを安全にします。??との併用:obj?.Prop ?? "Default"のパターンで、安全な取得とデフォルト値の設定を1行で記述できます。
冗長な if (obj != null) チェックを減らし、コードの意図を明確にするために積極的に活用すべき機能です。
