C# Tips | コレクション・LINQ:Null除外

C# C#
スポンサーリンク

はじめに:「Null除外」は“ゴミを先に掃き出してから考える”テクニック

業務コードを書いていると、ほぼ確実にこうなります。

データのどこかに null が紛れ込む
null を意識せずにプロパティにアクセスする
NullReferenceException で落ちる

この「null に毎回ビクビクする状態」から抜け出すための、超シンプルで強力な考え方が「Null除外」です。
つまり、「LINQ の入り口で null を全部はじいてしまう」ことで、その後の処理をスッキリさせる、という発想です。

ここでは、初心者向けに

Where での基本的な Null除外
OfType<T> を使った参照型の Null除外
「Null除外用ユーティリティ拡張メソッド」を自作する
業務コードでの具体的な使いどころ

を、例題付きでかみ砕いて説明していきます。


一番基本の形:Where で null を弾く

まずは「null じゃないものだけ」に絞る

一番ストレートな Null除外は、Where(x != null) です。

using System;
using System.Collections.Generic;
using System.Linq;

var names = new List<string?>
{
    "Alice",
    null,
    "Bob",
    null,
    "Charlie"
};

var nonNullNames = names
    .Where(x => x != null);

foreach (var name in nonNullNames)
{
    Console.WriteLine(name);
}
C#

出力はこうなります。

Alice
Bob
Charlie

null がきれいに取り除かれています。

ここでの重要ポイントは、「Where(x != null) を一発かませるだけで、“この先は null を気にしなくていい世界”を作れる」ということです。
この「入り口で掃除する」感覚が、Null除外の基本です。

でも型は string? のまま、という問題

上のコード、実は型的にはまだ少し気持ち悪いです。

IEnumerable<string?> nonNullNames = names.Where(x => x != null);
C#

C# の型システム的には、「Where(x != null) しても、型は string? のまま」です。
つまり、「実際には null はもういないのに、型上は“いるかもしれない”ことになっている」状態です。

このギャップを埋めるために、もう一歩踏み込んだ Null除外テクニックが出てきます。


参照型の Null除外に便利な OfType<T>

OfType<string>() で「null じゃない string だけ」にする

LINQ には OfType<T>() というメソッドがあります。
本来は「特定の型だけを取り出す」ためのものですが、参照型に対しては「null を除外する」効果もあります。

var names = new List<string?>
{
    "Alice",
    null,
    "Bob",
    null,
    "Charlie"
};

var nonNullNames = names
    .OfType<string>();   // ここがポイント

foreach (var name in nonNullNames)
{
    Console.WriteLine(name);
}
C#

これも出力は同じです。

Alice
Bob
Charlie

ただし、型が違います。

IEnumerable<string> nonNullNames = names.OfType<string>();
C#

ここでの重要ポイントは、「OfType<string>() を使うと、“null じゃない string だけ”になり、型も string(非 null)になる」ということです。
つまり、「実際の中身」と「型の世界」がきちんと一致します。

Where と OfType の違いを整理する

Where(x != null)
動き:null を除外する
型:IEnumerable<string?> のまま(nullable のまま)

OfType<string>()
動き:null を除外する
型:IEnumerable<string>(非 nullable)になる

「この先、null を気にせずに書きたい」なら、OfType<T> のほうが気持ちいいことが多いです。


Null除外ユーティリティ拡張メソッドを自作する

「NotNull」みたいな名前で 1 本用意しておく

毎回 OfType<string>() と書くのがちょっとダルい、という場合は、
「Null除外専用の拡張メソッド」を 1 本用意しておくと、コードがかなり読みやすくなります。

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

public static class NullFilterExtensions
{
    public static IEnumerable<T> NotNull<T>(
        this IEnumerable<T?> source)
        where T : class
    {
        return source.Where(x => x is not null)!;
    }
}
C#

使い方はこうです。

var names = new List<string?>
{
    "Alice",
    null,
    "Bob",
};

var nonNullNames = names.NotNull();

foreach (var name in nonNullNames)
{
    Console.WriteLine(name);
}
C#

ここでの重要ポイントは、「NotNull() という名前だけで、“ここで null を全部落としている”ことが一目で分かる」ことです。
Where(x != null) よりも、意図がはっきり読み取れます。

C# 11 以降なら「Null除外注釈」で型ももっと賢くできる

より厳密にやるなら、[NotNull][MemberNotNullWhen] などの属性を使って、
「このメソッドを通ったあとは null じゃない」とコンパイラに教えることもできます。

ただ、初心者のうちは「OfType<T> か、シンプルな NotNull() 拡張メソッド」くらいで十分です。
大事なのは、「Null除外を“パターン”として持っておく」ことです。


プロパティにアクセスする前に Null除外しておく

例:User? のリストから Name を取り出す

よくあるパターンを見てみます。

public class User
{
    public string Name { get; set; } = "";
}

var users = new List<User?>
{
    new User { Name = "Alice" },
    null,
    new User { Name = "Bob" }
};
C#

このとき、安直にこう書くと危険です。

var names = users
    .Select(u => u.Name);   // null のときに例外
C#

unull のときに u.Name で落ちます。

Null除外を先にやると、こうなります。

var names = users
    .OfType<User>()         // ここで null を除外
    .Select(u => u.Name);
C#

あるいは、自作の NotNull() を使うならこうです。

var names = users
    .NotNull()
    .Select(u => u.Name);
C#

ここでの重要ポイントは、「プロパティにアクセスする前に Null除外を挟む」という習慣です。
Select の中で ?. を乱発するより、「そもそも null を通さない」ほうが、ロジックがスッキリします。


SelectMany と組み合わせた Null除外

例:子コレクションが null のことがある場合

もう少しだけ実務寄りの例を出します。

public class Order
{
    public string Id { get; set; } = "";
    public List<OrderLine>? Lines { get; set; }
}

public class OrderLine
{
    public string Item { get; set; } = "";
    public int Amount { get; set; }
}
C#

Linesnull のことがあるとします。

var orders = new List<Order>
{
    new Order
    {
        Id = "O001",
        Lines = new List<OrderLine>
        {
            new OrderLine { Item = "A", Amount = 100 },
            new OrderLine { Item = "B", Amount = 200 },
        }
    },
    new Order
    {
        Id = "O002",
        Lines = null
    }
};
C#

全明細を 1 本のシーケンスにしたいとき、安直にこう書くと危険です。

var allLines = orders
    .SelectMany(o => o.Lines);   // Lines が null だと例外
C#

Null除外を挟むと、こうなります。

var allLines = orders
    .Select(o => o.Lines)
    .OfType<List<OrderLine>>()   // null の Lines を除外
    .SelectMany(lines => lines);
C#

あるいは、NotNull() を使うならこうです。

var allLines = orders
    .Select(o => o.Lines)
    .NotNull()
    .SelectMany(lines => lines);
C#

ここでの重要ポイントは、「SelectMany の前に、“子コレクションが null じゃないこと”を保証しておく」ということです。
Null除外をパターンとして持っていると、こういう場面で迷わなくなります。


「Null除外」を業務ユーティリティとしてどう位置づけるか

発想の転換:「null をどう扱うか」ではなく「null を通さない」

多くの初心者は、「null が来たらどうするか」を毎回 if で考えます。

if (x != null)
{
    // …
}
C#

でも、LINQ を使うときは発想を逆にして、

「そもそも null を流さないようにする」

と考えたほうが、コードがシンプルになります。

入り口で Where(x != null)OfType<T>NotNull() を挟む
その後の処理は「null じゃない前提」で書く

この「入り口で掃除してから流す」というパターンを、業務ユーティリティとしてチームで共有しておくと、
NullReferenceException に怯える時間がかなり減ります。

「null を許す設計」か「null を許さない設計」かを決める

もう一歩踏み込むと、設計レベルの話になります。

このコレクションは、null を含んでいてもいいのか
含んでいてもいいなら、どこで除外するのか
そもそも null を入れないように設計できないか

例えば、「ユーザー一覧に null が混ざっている」のは、そもそも設計として怪しいかもしれません。
一方、「外部システムから来るデータで null が混ざるのは仕方ない」なら、
「受け取った瞬間に Null除外して、内部では null を扱わない」という方針も取れます。

ここでの重要ポイントは、「Null除外は“場当たり的な対処”ではなく、“設計の一部”として考える」ということです。
そのための道具として、Where(x != null)OfType<T>NotNull() といったユーティリティを持っておく、という位置づけです。


まとめ:「Null除外ユーティリティ」は“null を早めに追い出すためのフィルター”

Null除外の本質は、

「後ろの処理で毎回 null を気にするくらいなら、
 最初に null を全部はじいてしまおう」

という発想です。

押さえておきたいポイントをもう一度整理すると、

Where(x => x != null) でシンプルに null を除外できる
参照型なら OfType<T>() を使うと、「中身も型も“非 null”」にできる
NotNull() のような拡張メソッドを 1 本用意しておくと、意図が読みやすくなる
プロパティにアクセスする前に Null除外を挟む、という習慣をつける
Null除外は「設計の一部」として、どこでやるかを決めておく

ここまで腹落ちしていれば、
「とりあえず ?. を付けてごまかす」段階から卒業して、
“null を早めに追い出す、きれいな LINQ パイプライン”を、自分で組み立てられるようになります。

タイトルとURLをコピーしました