データの順序を反転させる
C#で配列やリストなどのコレクションを扱う際、「データを末尾から先頭に向かって処理したい」や「時系列データを新しい順(降順)に並べ替えたい」といった場面があります。
LINQ(Language Integrated Query)のReverseメソッドを使用すると、元のデータ構造を変更することなく、順序が反転された新しいシーケンスを作成できます。
この記事では、LINQのReverseメソッドの基本的な使い方と、よく似た名前を持つList<T>.Reverseメソッドとの決定的な違いについて解説します。
LINQ Reverse メソッドの基本
System.Linq名前空間に含まれるReverse拡張メソッドは、シーケンス(IEnumerable<T>)の要素を逆順にした、新しいシーケンスを返します。
特徴:非破壊的(Non-Destructive)
LINQのReverseメソッドの最大の特徴は、元のコレクションを変更しないことです。元のデータの並び順はそのまま維持され、逆順になった「新しいデータ」が得られます。
コード例:駅名の順序を反転
電車の路線(駅名のリスト)を、逆方向の順序に変換する例です。
using System;
using System.Collections.Generic;
using System.Linq; // LINQを使用するために必須
public class LinqReverseExample
{
public static void Main()
{
// 下り路線の駅名リスト
var trainRoute = new List<string>
{
"Tokyo",
"Shinagawa",
"Kawasaki",
"Yokohama"
};
Console.WriteLine("--- 元の順序 (下り) ---");
Console.WriteLine(string.Join(" -> ", trainRoute));
// LINQの Reverse メソッドを使用
// 元の trainRoute は変更されず、逆順の新しいシーケンスが返される
// (明示的にジェネリック型 <string> を指定するか、AsEnumerable() を使うと確実です)
var returnRoute = trainRoute.AsEnumerable().Reverse();
Console.WriteLine("\n--- 逆順 (上り) ---");
Console.WriteLine(string.Join(" -> ", returnRoute));
// 元のリストが変更されていないことを確認
Console.WriteLine("\n--- 元のリストの再確認 ---");
Console.WriteLine(string.Join(" -> ", trainRoute));
}
}
出力結果:
--- 元の順序 (下り) ---
Tokyo -> Shinagawa -> Kawasaki -> Yokohama
--- 逆順 (上り) ---
Yokohama -> Kawasaki -> Shinagawa -> Tokyo
--- 元のリストの再確認 ---
Tokyo -> Shinagawa -> Kawasaki -> Yokohama
重要:List<T>.Reverse との違い
List<T>クラスには、LINQの拡張メソッドとは別に、クラス固有のReverseメソッドが存在します。これら2つは名前が同じですが、動作が大きく異なります。
1. LINQの Reverse()
- 構文:
IEnumerable<T> result = source.Reverse(); - 戻り値: 逆順になった新しいシーケンス(
IEnumerable<T>)。 - 動作: 元のリストは変更しません(非破壊的)。
2. List<T>の Reverse()
- 構文:
list.Reverse(); - 戻り値:
void(なし)。 - 動作: 元のリストの中身を直接書き換えます(破壊的 / インプレース)。
比較コード
以下のコードは、この2つのメソッドの挙動の違いを示しています。
using System;
using System.Collections.Generic;
using System.Linq;
public class ReverseDifferenceExample
{
public static void Main()
{
// --- ケース1: List<T>.Reverse() (破壊的) ---
var numbersList = new List<int> { 1, 2, 3 };
// 戻り値は void。numbersList 自体が変更される
numbersList.Reverse();
Console.WriteLine($"List.Reverse後: {string.Join(", ", numbersList)}"); // 3, 2, 1
// --- ケース2: LINQ Reverse() (非破壊的) ---
var numbersSeq = new List<int> { 1, 2, 3 };
// リストに対して LINQ の Reverse を使う場合、
// AsEnumerable() を挟むか、明示的に拡張メソッドとして呼び出すのが一般的
var reversedSeq = numbersSeq.AsEnumerable().Reverse();
Console.WriteLine($"LINQ Reverse結果: {string.Join(", ", reversedSeq)}"); // 3, 2, 1
Console.WriteLine($"元のリスト: {string.Join(", ", numbersSeq)}"); // 1, 2, 3 (変わっていない)
}
}
出力結果:
List.Reverse後: 3, 2, 1
LINQ Reverse結果: 3, 2, 1
元のリスト: 1, 2, 3
List<T>型の変数に対して単に.Reverse()と記述すると、C#はインスタンスメソッドであるList<T>.Reverse()(破壊的変更)を優先して呼び出します。LINQ版を使用したい場合は、.AsEnumerable().Reverse()と記述するか、Enumerable.Reverse(list)のように呼び出す必要があります。
文字列の反転について
string型はIEnumerable<char>を実装しているため、LINQのReverseメソッドを適用できますが、戻り値はIEnumerable<char>となります。文字列(string)として結果を得るには、再度結合する必要があります。
string text = "C# Programming";
// Reverse() で char のシーケンスにし、ToArray() で配列にしてから string コンストラクタへ
string reversedText = new string(text.Reverse().ToArray());
Console.WriteLine(reversedText); // "gnimmargorP #C"
(注: サロゲートペアや結合文字を含む文字列の場合、単純なReverseでは文字が壊れる可能性があります。そのような場合はStringInfoクラスを使用するなどの工夫が必要です。)
まとめ
LINQ Reverse(): 元のコレクションを変更せず、逆順の新しいシーケンスを取得したい場合に使用します。List<T>.Reverse(): メモリ効率を重視し、元のリスト自体を逆順に書き換えても問題ない場合に使用します。
意図しないデータの変更を防ぐため、どちらのメソッドを使用しているかを常に意識することが重要です。
