はじめに
あるとき、同じプロジェクト内に == null と is null という2つの書き方が混在していることに気づきました。
if (hoge == null) { } // こっちの書き方と
if (hoge is null) { } // こっちの書き方が混在している
「どっちでも動くんだから同じでしょ」と思いつつも、なんとなく気になってそのままにしていました。どちらを使うべきか基準がないとコードの一貫性が保てないので、ちゃんと調べてみることにしました。
環境
- .NET 10.0
- C# 13.0
- Visual Studio 2026
結論から言うと
通常のクラスではほぼ同じ動作になります。ただ、is null の方が安全で、最近は推奨される書き方です。
「ほぼ」という言葉が気になったので、違いを整理しました。
違いの核心:== はオーバーロードできる
== 演算子は、クラス側でオーバーロード(独自の動作に上書き)できます。
public class Hoge
{
public static bool operator ==(Hoge a, Hoge b)
{
return true; // 常にtrueを返す独自実装(極端な例)
}
public static bool operator !=(Hoge a, Hoge b)
{
return false;
}
}
var hoge = new Hoge();
Console.WriteLine(hoge == null); // true ← オーバーロードによって意図しない結果に
Console.WriteLine(hoge is null); // false ← 実際にnullではないので正しい
== null は、もしそのクラスが == を独自実装していると、その実装を経由した結果が返ってきます。null かどうかを調べたいだけなのに、全く関係のない処理が走る可能性があるということです。
一方、is null はパターンマッチングの構文として、== のオーバーロードを無視し、参照が本当に null かどうかだけを判定します。
CLR(Common Language Runtime)とは?
CLR は .NET の実行エンジンのことです。C# で書いたコードはコンパイルされた後、最終的にこの CLR が動かしています。is null を使うと、余計な処理(== のオーバーロードなど)を経由せず、「2つの参照が同じ場所を指しているか」という最も基本的な比較だけを行います。
is null の「パターンマッチング」とは何か
パターンマッチングとは、「値があるパターン(形・条件)に一致するか」を簡潔に書ける構文です。is キーワードや switch 式で使います。
is null は「定数パターン」
hoge is null は、パターンマッチングの中でも「定数パターン」と呼ばれる種類です。null という定数に一致するかを調べています。
定数パターンは null 専用というわけではなく、数値や文字列などの定数にも使えます。
if (hoge is null) { } // null に一致するか
if (hoge is not null) { } // null でないか
if (num is 42) { } // 42 に一致するか
if (flag is true) { } // true に一致するか
if (name is "Alice") { } // 特定の文字列に一致するか
ただ、num is 42 より num == 42 の方が自然に読めるので、数値や文字列の比較では == を使う場面の方が多いと思います。
null チェックのときだけ is が特に重要な理由があります。int などの数値型は値型なのでそもそも null にはなれません(int? のような Nullable にしない限り)。string は参照型ですが、== が「文字列の中身を比較する」よう標準でオーバーロードされているため、null との比較は想定通り動きます。問題になりうるのは自作クラスで == を独自実装しているケースです。null チェックの対象はほぼ必ずクラスのインスタンスなので、== の独自実装の影響を受けない is null が安全、というわけです。
switch 式でも使える
パターンマッチングは switch 式でも使えます。
// 旧来の switch 文
string GetLabel(int score)
{
switch (score)
{
case int n when n >= 90: return "A";
case int n when n >= 70: return "B";
default: return "C";
}
}
// switch 式+パターンマッチング(C# 8〜)
string GetLabel(int score) => score switch
{
>= 90 => "A",
>= 70 => "B",
_ => "C" // _ はデフォルト(ワイルドカード)
};
switch 式は is とは別の構文ですが、同じ「パターンマッチング」の仕組みを使っています。条件分岐をすっきり書けるので、慣れると手放せなくなります。
is 自体は昔からある
is キーワード自体は C# 1 からある型チェックの構文でした。
if (obj is string) { } // 昔からある型チェック(C# 1〜)
C# 7 以降でパターンマッチングとして機能が拡張され、右辺にいろいろ書けるようになりました。
if (obj is string s) { } // 型チェック+変数への代入が1行で(C# 7〜)
if (obj is null) { } // null との比較(C# 7〜)
if (num is >= 18) { } // 関係パターン(C# 9〜)
型チェックと変数への代入が一行で書けるのは地味に便利で、以前は is でチェックしてから別途キャストするという2ステップが必要だったのが1行になっています。
// 旧来の書き方(2ステップ)
if (obj is string)
{
var s = (string)obj;
Console.WriteLine(s.Length);
}
// パターンマッチング(1ステップ)
if (obj is string s)
{
Console.WriteLine(s.Length); // s はここで使える
}
!= null と is not null も同様
null チェックの逆パターンも同じ話です。
if (hoge is not null) { } // 推奨
if (hoge != null) { } // != のオーバーロードを受ける可能性あり
!= も == と同様にオーバーロードできるため、is not null の方が安全です。
LINQ の条件式では注意が必要
LINQ の種類によっては is が使えない場合があります。
List<T> などのコレクションに対する通常の LINQ(IEnumerable<T> ベース)では is not null は問題なく動きます。
var list = new List<string?> { "Alice", null, "Bob" };
// IEnumerable ベースなら問題なし
var result = list.Where(x => x is not null).ToList();
一方、Entity Framework Core のようにクエリを SQL に変換する LINQ(IQueryable<T> ベース)では is が使えず、コンパイルエラーになります。
Entity Framework Core とは?
C# のコードを SQL に変換してデータベースを操作できるライブラリです。Where(x => ...) の中に書いた条件式を SQL の WHERE 句に変換する仕組みになっています。ここで受け取るのが Func<T, bool> ではなく Expression<Func<T, bool>> という式ツリーで、C# コンパイラが is パターンを式ツリーに変換することに対応していないため、コンパイルエラー(CS8122)になります。EF Core に限らず、Expression<> を引数に取る設計のライブラリ全般で同じ制約が生じます。
// IQueryable に対しては is が使えない(EF Core など)
var result = dbContext.Users
.Where(x => x.Name is not null) // NG:CS8122 コンパイルエラー
.ToList();
// こちらを使う
var result = dbContext.Users
.Where(x => x.Name != null) // OK
.ToList();
is null / is not null を推奨と書きましたが、LINQ を書くときは状況によって == null / != null を使い分ける必要があります。
いつ == null を使う場面があるか
== のオーバーロードを意図的に使いたい場面では == null を選ぶこともあります。そのクラスが定義した独自の等値ロジックを走らせたい場合です。
ただ、これはかなりのレアケースです。普段の開発で「null チェックしたいだけ」という場面なら、迷わず is null を選んで問題ないと思います。
まとめ
-
== nullは==演算子のオーバーロードの影響を受けるため、クラスによっては意図しない動作をする可能性がある -
is nullはパターンマッチングの構文で、オーバーロードを無視して参照が本当に null かどうかだけを見る - パフォーマンス差はほぼない
-
is null/is not nullが C# 7 以降の推奨される書き方 -
==のオーバーロードを意図的に使いたい特殊な場合を除いて、基本はis nullを使えばよい - ただし
Expression<>を引数に取る設計のライブラリ(EF Core など)ではisが使えないので、その場合は== null/!= nullを使う
調べる前は「どっちでも同じでしょ」と思っていましたが、== がオーバーロードできるという仕組みを知ると、is null が推奨される理由がすっきり納得できました。プロジェクト内で書き方が混在しているのも気になっていたので、これを機に通常のコードは is null に統一しようと思います。
参考になったら いいね や ストック をお願いします!
同じような疑問を持ったことがある方のコメントもお待ちしています。
この記事のコメントの内容が的確すぎて一部編集させていただきました。
ぜひ、一読ください。
>> System.Linq.Expressionsでは構文の変換に対応していない
参考
- is 演算子 - C# リファレンス(Microsoft Learn)
- パターン マッチング - C# リファレンス(Microsoft Learn)
- 等値演算子 - C# リファレンス(Microsoft Learn)
関連リンク
技術ブログでも学びや検証内容をまとめています。
アウトプットで手当がもらえる会社 ONE WEDGE
株式会社ONE WEDGE では一緒に働く仲間を募集中!
技術記事を書くと手当がもらえる「IT系記事寄稿特別手当」という制度があります。
興味があればぜひカジュアルに話しましょう!
👉 採用サイト