【C#】LINQのLastとLastOrDefault:最後の要素を取得する方法と例外の回避

目次

コレクションの末尾要素へのアクセス

配列やリストなどのコレクションを扱う際、「リストの最後に追加されたデータ(最新のデータ)を取得したい」や、「特定の条件に合致するもののうち、一番最後のものを探したい」という場面は頻繁に発生します。

LINQ(Language Integrated Query)は、この目的のためにLastメソッドとLastOrDefaultメソッドを提供しています。これらは前回の記事で解説したFirst / FirstOrDefault(先頭を取得)と対になる機能であり、**シーケンスの末尾(後ろ)**から要素を探索します。

この記事では、これら2つのメソッドの動作の違いと、例外(エラー)を回避するための正しい使い分けについて解説します。


Lastメソッド:要素が「必ず存在する」場合

Lastメソッドは、コレクションの最後の要素、または条件に一致する最後の要素を返します。

このメソッドの最大の特徴は、Firstと同様に、要素が見つからない場合に例外(InvalidOperationException)をスローする点です。

したがって、Lastは「ロジック上、その要素は確実に存在するはずだ(空のリストに対して呼ぶことはない)」という前提がある場合にのみ使用します。

コード例:Lastの使用

アクセスログのリストから、最も新しい(リストの末尾にある)ログを取得する例です。

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

public class AccessLog
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public string Action { get; set; }
}

public class LastExample
{
    public static void Main()
    {
        var accessLogs = new List<AccessLog>
        {
            new AccessLog { Id = 1, UserName = "UserA", Action = "Login" },
            new AccessLog { Id = 2, UserName = "UserB", Action = "Login" },
            new AccessLog { Id = 3, UserName = "UserA", Action = "ViewPage" },
            new AccessLog { Id = 4, UserName = "UserC", Action = "Login" },
            new AccessLog { Id = 5, UserName = "UserA", Action = "Logout" }
        };

        // 1. 引数なし: リストの「最後」の要素を取得
        var lastLog = accessLogs.Last();
        Console.WriteLine($"最後のログ: ID={lastLog.Id}, User={lastLog.UserName}, Action={lastLog.Action}");

        // 2. 条件指定: UserB の最後の操作を取得
        var lastActionByUserB = accessLogs.Last(log => log.UserName == "UserB");
        Console.WriteLine($"UserBの最後の操作: {lastActionByUserB.Action}");

        // 3. 要素が見つからない場合 (例外発生)
        try
        {
            // "UserZ" は存在しないため、例外がスローされる
            var errorLog = accessLogs.Last(log => log.UserName == "UserZ");
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine($"エラー発生: {ex.Message}");
        }
    }
}

出力結果:

最後のログ: ID=5, User=UserA, Action=Logout
UserBの最後の操作: Login
エラー発生: Sequence contains no matching element

LastOrDefaultメソッド:要素が「ないかもしれない」場合

LastOrDefaultメソッドも同様に最後の要素を探しますが、要素が見つからなかった場合に例外をスローせず、その型の「既定値(デフォルト値)」を返します

  • 参照型(string, クラスなど)の既定値: null
  • 数値型(int, doubleなど)の既定値: 0

リストが空である可能性や、条件に一致するデータが存在しない可能性がある場合は、LastではなくLastOrDefaultを使用することで、安全に処理を続行できます。

コード例:LastOrDefaultの使用

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

public class LastOrDefaultExample
{
    public static void Main()
    {
        var errorCodes = new List<int> { 404, 500, 403, 401 };

        // 1. 条件に一致する要素がある場合
        // 500番台のエラーの中で最後のものを探す
        int serverError = errorCodes.LastOrDefault(code => code >= 500);
        Console.WriteLine($"最後のサーバーエラー: {serverError}"); // 500

        // 2. 条件に一致する要素がない場合
        // 300番台のエラーはないため、0 (intの既定値) が返される
        int redirectError = errorCodes.LastOrDefault(code => code >= 300 && code < 400);

        // 見つからなかった場合の判定 (0 かどうか)
        if (redirectError == 0)
        {
            Console.WriteLine("リダイレクト系のエラーは見つかりませんでした。");
        }
        else
        {
            Console.WriteLine($"最後のリダイレクト: {redirectError}");
        }

        // 3. 参照型 (string) の場合
        var messages = new List<string>(); // 空のリスト
        string? lastMessage = messages.LastOrDefault(); // null が返る

        Console.WriteLine($"メッセージ: {lastMessage ?? "<なし>"}");
    }
}

出力結果:

最後のサーバーエラー: 500
リダイレクト系のエラーは見つかりませんでした。
メッセージ: <なし>

First系とのパフォーマンスの違い

LastLastOrDefaultを使用する際、パフォーマンスに注意が必要なケースがあります。

  • List<T>や配列の場合: インデックスを使用して直接末尾にアクセスできるため、非常に高速です。
  • IEnumerable<T>(遅延評価のシーケンス)の場合: 末尾がどこにあるか分からないため、シーケンスの最初から最後まで全ての要素を走査します。要素数が膨大な場合、First(最初だけ見て終わる)に比べて処理時間が長くなる可能性があります。

まとめ

  • Last():
    • 要素が必ず存在すると確信できる場合に使用します。
    • リストが空、または条件不一致の場合は例外が発生します。
  • LastOrDefault():
    • 要素が存在しない可能性がある場合に使用します。
    • 戻り値が既定値(null0)かどうかをチェックする必要があります。

基本的には、First系と同様に、予期せぬ例外によるクラッシュを防ぐため、LastOrDefaultを使用し、戻り値のnullチェックを行うパターンが安全で推奨されます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次