【C#】LINQでサクッと文字列抽出・変形・連結を実現するテクニック大全
C#のLINQを使えば、文字列をコレクションとして扱えます。
Where
で条件抽出、Select
で部分取り出し、Aggregate
で連結というように、日常的な処理を1行レベルで記述でき、正規表現や複雑なループを減らせます。
パフォーマンスは標準関数と同等で、可読性が高まり保守も楽になります。
LINQとは何か
LINQ(Language Integrated Query)は、C#に組み込まれた強力なクエリ機能で、データの検索や操作を簡潔に記述できる仕組みです。
もともとはデータベースのクエリ言語として発展しましたが、現在では配列やリスト、XML、さらには文字列など、さまざまなデータソースに対して利用できます。
LINQを使うことで、複雑なループや条件分岐を減らし、読みやすく保守しやすいコードを書くことが可能です。
LINQによる文字列処理のメリット
文字列は単なるテキストの塊のように見えますが、実は文字の並び(シーケンス)として扱えます。
LINQを使うと、この文字のシーケンスに対して直感的にクエリを実行できるため、文字列の抽出や変形、連結などの操作が非常に効率的になります。
具体的なメリットは以下の通りです。
- コードの簡潔化
文字列の特定の条件に合う文字だけを抽出したり、変換したりする処理を、複雑なループや条件分岐なしで書けます。
例えば、数字だけを取り出す処理が1行で書けることもあります。
- 読みやすさの向上
LINQのクエリ構文やメソッドチェーンは、処理の意図が明確に表現されるため、他の開発者がコードを理解しやすくなります。
- 遅延実行によるパフォーマンス最適化
LINQは遅延実行を基本としているため、必要なデータだけを効率的に処理できます。
大きな文字列や大量のデータを扱う際に無駄な処理を減らせます。
- 豊富な組み込みメソッドの活用
Where
やSelect
、Aggregate
などの標準的なメソッドを使うことで、文字列のフィルタリングや変換、集約が簡単に行えます。
- 他のデータソースとの親和性
LINQは文字列だけでなく、配列やリスト、XML、データベースなど多様なデータソースに対して同じような操作が可能です。
文字列操作の知識が他の領域でも活かせます。
このように、LINQを使うことで文字列操作のコードがシンプルかつパワフルになり、開発効率が大幅にアップします。
文字列をシーケンスとして扱う考え方
C#の文字列はSystem.String
型で表されますが、実はIEnumerable<char>
インターフェースを実装しているため、文字のシーケンスとして扱えます。
つまり、文字列は「文字の並び」としてLINQの対象になるのです。
例えば、文字列"Hello"
は、'H'
, 'e'
, 'l'
, 'l'
, 'o'
という5つの文字のシーケンスとみなせます。
LINQのWhere
やSelect
などのメソッドは、このシーケンスの各要素(文字)に対して処理を行います。
この考え方を理解すると、文字列の操作がより柔軟になります。
たとえば、
- 文字列から特定の条件に合う文字だけを抽出する
- 文字ごとに大文字・小文字変換を行う
- 文字のUnicodeコードポイントを取得する
- 文字列を文字単位で分割・変換して新しい文字列を作る
といった処理が、LINQのメソッドチェーンで簡単に書けます。
また、文字列を単なる文字の集合として扱うことで、文字列の一部を取り出したり、条件に合う文字だけを選んだり、文字列の並びを変えたりする操作が直感的に行えます。
以下のようなイメージです。
- 文字列 → 文字のシーケンス
IEnumerable<char>
- LINQの
Where
で条件に合う文字だけ抽出 - LINQの
Select
で文字を変換 - LINQの
Aggregate
で文字を連結して新しい文字列を生成
このように、文字列をシーケンスとして扱うことで、LINQの強力な機能を活用した文字列操作が可能になります。
これがLINQを使った文字列処理の基本的な考え方です。
前提知識:基本構文のおさらい
クエリ構文とメソッド構文の違い
LINQには主に2つの書き方があります。
ひとつはSQLに似た「クエリ構文」、もうひとつはメソッドチェーンで記述する「メソッド構文」です。
どちらも同じ処理を実現できますが、用途や好みによって使い分けられます。
クエリ構文
クエリ構文は、from
、where
、select
などのキーワードを使い、SQLのような見た目で記述します。
読みやすく直感的なため、複雑な条件を扱う場合に便利です。
例:文字列から数字だけを抽出するクエリ構文
string input = "A1B2C3";
var digits = from ch in input
where char.IsDigit(ch)
select ch;
foreach (var digit in digits)
{
Console.Write(digit + " ");
}
// 出力: 1 2 3
メソッド構文
メソッド構文は、Where
やSelect
などの拡張メソッドを連結して書きます。
ラムダ式を使うため柔軟で、メソッドチェーンの形で処理の流れがわかりやすいです。
同じ処理をメソッド構文で書くと以下のようになります。
string input = "A1B2C3";
var digits = input.Where(ch => char.IsDigit(ch));
foreach (var digit in digits)
{
Console.Write(digit + " ");
}
// 出力: 1 2 3
選び方のポイント
- 簡単なフィルタリングや変換ならメソッド構文が短くて便利です
- 複雑な結合やグループ化を行う場合はクエリ構文のほうが読みやすいことがあります
- 実際には両者を混在させることも可能です
デリゲートとラムダ式の基礎
LINQのメソッド構文では、条件や変換のロジックを引数として渡す必要があります。
これを実現するのが「デリゲート」と「ラムダ式」です。
デリゲートとは
デリゲートは、メソッドの参照を保持できる型で、関数ポインタのような役割を果たします。
LINQのWhere
やSelect
は、条件や変換を表すデリゲートを受け取ります。
例えば、Func<char, bool>
は「char
を受け取ってbool
を返す関数」を表します。
ラムダ式とは
ラムダ式は匿名関数の一種で、簡潔に関数を定義できます。
LINQではラムダ式を使って条件や変換を記述するのが一般的です。
例:文字が数字かどうかを判定するラムダ式
ch => char.IsDigit(ch)
これは「引数ch
を受け取り、char.IsDigit(ch)
の結果を返す関数」を意味します。
実際の使い方
string input = "A1B2C3";
var digits = input.Where(ch => char.IsDigit(ch));
ここでch => char.IsDigit(ch)
がWhere
メソッドに渡されるデリゲートです。
IEnumerable<char>とIEnumerable<string>
LINQはIEnumerable<T>
インターフェースを実装したシーケンスに対して操作を行います。
文字列はIEnumerable<char>
として扱えますが、単語の集合などはIEnumerable<string>
となります。
IEnumerable<char>
文字列は文字のシーケンスなので、IEnumerable<char>
として扱えます。
これにより、文字単位でのフィルタリングや変換が可能です。
例:文字列の中から母音だけを抽出する
string text = "Hello World";
var vowels = text.Where(ch => "aeiouAEIOU".Contains(ch));
foreach (var v in vowels)
{
Console.Write(v + " ");
}
// 出力: e o o
IEnumerable<string>
単語の集合や文字列の配列はIEnumerable<string>
です。
単語単位での操作が可能で、例えば長さでフィルタしたり、先頭文字だけを取り出したりできます。
例:長さが3の単語を抽出する
string[] words = { "the", "quick", "fox", "jumps" };
var shortWords = words.Where(word => word.Length == 3);
foreach (var word in shortWords)
{
Console.WriteLine(word);
}
// 出力:
// the
// fox
- 文字列は
IEnumerable<char>
として扱い、文字単位の操作ができます - 単語や文字列の集合は
IEnumerable<string>
として扱い、単語単位の操作ができます - LINQのメソッドはどちらのシーケンスにも適用可能で、用途に応じて使い分けます
抽出: 条件に合う文字や単語を取り出す
Charメソッドと組み合わせたフィルタリング
C#のChar
クラスには文字の種類を判定する便利なメソッドが多数用意されています。
LINQのWhere
メソッドと組み合わせることで、文字列から特定の条件に合う文字だけを簡単に抽出できます。
数字だけを拾う
文字列から数字だけを抽出するには、Char.IsDigit
メソッドを使います。
Where
で文字ごとに判定し、数字だけを取り出せます。
using System;
using System.Linq;
class Program
{
static void Main()
{
string input = "ABCDE99F-J74-12-89A";
var digits = input.Where(ch => Char.IsDigit(ch));
Console.WriteLine("数字だけを抽出:");
foreach (var digit in digits)
{
Console.Write(digit + " ");
}
}
}
数字だけを抽出:
9 9 7 4 1 2 8 9
このコードでは、Char.IsDigit
が文字が数字かどうかを判定し、数字だけが抽出されています。
アルファベットのみを抽出
アルファベットだけを取り出したい場合は、Char.IsLetter
を使います。
大文字・小文字を問わずアルファベットを抽出できます。
using System;
using System.Linq;
class Program
{
static void Main()
{
string input = "Hello123World!";
var letters = input.Where(ch => Char.IsLetter(ch));
Console.WriteLine("アルファベットのみを抽出:");
foreach (var letter in letters)
{
Console.Write(letter + " ");
}
}
}
アルファベットのみを抽出:
H e l l o W o r l d
特定文字集合でフィルタ
特定の文字だけを抽出したい場合は、文字集合を用意してContains
メソッドで判定します。
例えば、母音だけを抽出する例です。
using System;
using System.Linq;
class Program
{
static void Main()
{
string input = "This is an example sentence.";
char[] vowels = { 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U' };
var extracted = input.Where(ch => vowels.Contains(ch));
Console.WriteLine("母音だけを抽出:");
foreach (var ch in extracted)
{
Console.Write(ch + " ");
}
}
}
母音だけを抽出:
i i a e a e e e
このように、任意の文字集合を用意してフィルタリングできます。
正規表現との併用
LINQのWhere
メソッドは任意の条件を指定できるため、正規表現を使った複雑なパターンマッチングも可能です。
System.Text.RegularExpressions.Regex
クラスと組み合わせて使います。
Regex.IsMatchをWhereで使う
文字列の配列や単語リストから、正規表現にマッチするものだけを抽出する例です。
using System;
using System.Linq;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
string[] words = { "apple", "banana", "cherry", "date", "fig", "grape" };
var pattern = @"^[a-d]"; // a〜dで始まる単語
var filtered = words.Where(word => Regex.IsMatch(word, pattern));
Console.WriteLine("a〜dで始まる単語:");
foreach (var word in filtered)
{
Console.WriteLine(word);
}
}
}
a〜dで始まる単語:
apple
banana
cherry
date
括弧で囲まれた部分を取得
文字列中の括弧で囲まれた部分だけを抽出したい場合は、Regex.Matches
を使い、LINQで結果を列挙します。
using System;
using System.Linq;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
string input = "This is a test (sample) string with (multiple) parentheses.";
var matches = Regex.Matches(input, @"\(([^)]*)\)")
.Cast<Match>()
.Select(m => m.Groups[1].Value);
Console.WriteLine("括弧内の文字列:");
foreach (var match in matches)
{
Console.WriteLine(match);
}
}
}
括弧内の文字列:
sample
multiple
この例では、正規表現で括弧内の文字列をキャプチャし、LINQのSelect
で抽出しています。
インデックスベースの抽出
LINQのSkip
やTake
メソッドを使うと、文字列や配列の特定の範囲を簡単に切り出せます。
インデックスを意識した抽出も可能です。
SkipとTakeで範囲を切り取る
文字列の先頭や途中から一定数の文字を取り出す例です。
using System;
using System.Linq;
class Program
{
static void Main()
{
string input = "HelloWorld";
var middle = input.Skip(2).Take(5);
Console.WriteLine("3文字目から5文字を抽出:");
foreach (var ch in middle)
{
Console.Write(ch);
}
}
}
3文字目から5文字を抽出:
lloWo
Skip(2)
で先頭2文字を飛ばし、Take(5)
で次の5文字を取得しています。
インターバル抽出のアイデア
例えば、文字列の偶数番目の文字だけを抽出したい場合は、Select
でインデックスを取得し、Where
で偶数かどうかを判定します。
using System;
using System.Linq;
class Program
{
static void Main()
{
string input = "ABCDEFGHIJ";
var evenIndexChars = input.Select((ch, index) => new { ch, index })
.Where(x => x.index % 2 == 0)
.Select(x => x.ch);
Console.WriteLine("偶数番目の文字:");
foreach (var ch in evenIndexChars)
{
Console.Write(ch + " ");
}
}
}
偶数番目の文字:
A C E G I
このように、インデックスを活用した抽出もLINQで簡単に実現できます。
変形: 文字列を別形式に加工する
Selectで個々の要素を加工
LINQのSelect
メソッドは、シーケンスの各要素に対して変換処理を行い、新しいシーケンスを生成します。
文字列をIEnumerable<char>
として扱い、文字単位で大文字・小文字変換やUnicodeコードポイントの取得など、さまざまな加工が可能です。
大文字小文字の変換
文字列の各文字を大文字や小文字に変換するには、Select
でchar.ToUpper
やchar.ToLower
を使います。
変換後はstring.Concat
で再び文字列に結合します。
using System;
using System.Linq;
class Program
{
static void Main()
{
string input = "Hello World!";
var upperChars = input.Select(ch => char.ToUpper(ch));
var upperString = string.Concat(upperChars);
Console.WriteLine("大文字変換結果:");
Console.WriteLine(upperString);
}
}
大文字変換結果:
HELLO WORLD!
同様に小文字変換も簡単です。
var lowerString = string.Concat(input.Select(ch => char.ToLower(ch)));
Unicodeコードポイント取得
文字列の各文字のUnicodeコードポイント(整数値)を取得することもできます。
Select
で(int)ch
とキャストすればOKです。
using System;
using System.Linq;
class Program
{
static void Main()
{
string input = "ABC";
var codePoints = input.Select(ch => (int)ch);
Console.WriteLine("Unicodeコードポイント:");
foreach (var code in codePoints)
{
Console.WriteLine(code);
}
}
}
Unicodeコードポイント:
65
66
67
このように、文字列の各文字を数値に変換して処理できます。
SelectManyでネストを平坦化
SelectMany
は、各要素から複数の要素を生成し、それらを一つのシーケンスに平坦化します。
文字列のリストから文字のリストを作るときに便利です。
単語リストを文字リストへ
単語のリストを文字単位に分解し、すべての文字を一つのシーケンスにまとめる例です。
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> words = new List<string> { "apple", "banana", "cherry" };
var allChars = words.SelectMany(word => word);
Console.WriteLine("単語リストを文字リストに変換:");
foreach (var ch in allChars)
{
Console.Write(ch + " ");
}
}
}
単語リストを文字リストに変換:
a p p l e b a n a n a c h e r r y
SelectMany
を使うことで、ネストしたシーケンスを平坦化し、文字単位での処理がしやすくなります。
Projection with anonymous types
LINQのSelect
では、匿名型を使って複数の情報をまとめて返すことができます。
文字列とその長さのペアを作る例を紹介します。
元の文字列と長さのペアを作る
文字列のリストから、各文字列とその長さを持つ匿名型のシーケンスを生成します。
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> words = new List<string> { "apple", "banana", "cherry" };
var wordInfo = words.Select(word => new { Text = word, Length = word.Length });
Console.WriteLine("文字列と長さのペア:");
foreach (var info in wordInfo)
{
Console.WriteLine($"単語: {info.Text}, 長さ: {info.Length}");
}
}
}
文字列と長さのペア:
単語: apple, 長さ: 5
単語: banana, 長さ: 6
単語: cherry, 長さ: 6
匿名型を使うことで、複数の関連情報をまとめて扱いやすくなります。
Zipで2つの文字列を合成
Zip
メソッドは2つのシーケンスを要素ごとに結合し、新しいシーケンスを作ります。
文字列同士を組み合わせる際に便利です。
マスク付き合成パターン
例えば、マスク文字列を使って元の文字列の一部を隠す処理を考えます。
マスク文字列の'*'
の位置は'*'
に置き換え、それ以外は元の文字を使います。
using System;
using System.Linq;
class Program
{
static void Main()
{
string original = "SensitiveData";
string mask = "***aaa****a**";
var masked = new string(original.Zip(mask, (o, m) => m == '*' ? '*' : o).ToArray());
Console.WriteLine("マスク付き合成結果:");
Console.WriteLine(masked);
}
}
マスク付き合成結果:
***sit****a**
この例では、Zip
で2つの文字列の対応する文字を比較し、マスク文字が'*'
なら'*'
を、そうでなければ元の文字を採用しています。
ToArray
で文字配列に変換し、new string
で文字列に戻しています。
このようにZip
を使うと、2つの文字列を要素単位で組み合わせて新しい文字列を作ることができます。
整列・ソート: 文字列を順序付ける
OrderByでアルファベット順
LINQのOrderBy
メソッドは、シーケンスの要素を指定したキーに基づいて昇順に並べ替えます。
文字列のリストや文字のシーケンスをアルファベット順にソートしたい場合に使います。
以下は文字列の配列をアルファベット順に並べ替える例です。
using System;
using System.Linq;
class Program
{
static void Main()
{
string[] words = { "banana", "apple", "cherry", "date" };
var sortedWords = words.OrderBy(word => word);
Console.WriteLine("アルファベット順にソート:");
foreach (var word in sortedWords)
{
Console.WriteLine(word);
}
}
}
アルファベット順にソート:
apple
banana
cherry
date
この例では、OrderBy
に文字列自体をキーとして渡しているため、辞書順(アルファベット順)に並び替えられています。
文字列の各文字をアルファベット順に並べ替えることも可能です。
string input = "LINQ";
var sortedChars = input.OrderBy(ch => ch);
string result = new string(sortedChars.ToArray());
Console.WriteLine("文字列の文字をアルファベット順にソート:");
Console.WriteLine(result);
文字列の文字をアルファベット順にソート:
ILNQ
ThenByで複合キーソート
OrderBy
で一次ソートを行った後、ThenBy
を使うと二次ソート、三次ソートと複数のキーで順序付けができます。
複雑な条件でのソートに便利です。
例えば、単語の長さで昇順に並べ替え、同じ長さの単語はアルファベット順に並べる例です。
using System;
using System.Linq;
class Program
{
static void Main()
{
string[] words = { "apple", "bat", "banana", "cat", "dog", "apricot" };
var sortedWords = words.OrderBy(word => word.Length)
.ThenBy(word => word);
Console.WriteLine("長さで昇順、同じ長さはアルファベット順にソート:");
foreach (var word in sortedWords)
{
Console.WriteLine(word);
}
}
}
長さで昇順、同じ長さはアルファベット順にソート:
bat
cat
dog
apple
banana
apricot
このように、OrderBy
でまず長さ順に並べ替え、ThenBy
で同じ長さの単語をアルファベット順に整列しています。
Reverseで反転
Reverse
メソッドはシーケンスの要素の順序を逆にします。
ソート結果を逆順にしたい場合や、元の順序を反転させたい場合に使います。
例えば、先ほどのアルファベット順にソートした単語リストを逆順に表示する例です。
using System;
using System.Linq;
class Program
{
static void Main()
{
string[] words = { "banana", "apple", "cherry", "date" };
var reversedWords = words.OrderBy(word => word).Reverse();
Console.WriteLine("アルファベット順の逆順:");
foreach (var word in reversedWords)
{
Console.WriteLine(word);
}
}
}
アルファベット順の逆順:
date
cherry
banana
apple
また、文字列の文字を逆順に並べ替えることも簡単です。
string input = "LINQ";
var reversedChars = input.Reverse();
string result = new string(reversedChars.ToArray());
Console.WriteLine("文字列の文字を逆順に:");
Console.WriteLine(result);
文字列の文字を逆順に:
QNIL
Reverse
は元のシーケンスを変更せず、新しい逆順のシーケンスを返すため、元のデータはそのまま保持されます。
集約: 連結と統計
Aggregateで自由に連結
LINQのAggregate
メソッドは、シーケンスの要素を1つにまとめるための強力な集約関数です。
文字列の連結や複雑な結合処理を自由にカスタマイズして実装できます。
区切り文字付き連結
文字列のリストをカンマやスペースなどの区切り文字で連結する場合、Aggregate
を使うと簡潔に書けます。
以下はカンマ区切りで連結する例です。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<string> fruits = new List<string> { "りんご", "ばなな", "みかん" };
string result = fruits.Aggregate((current, next) => current + ", " + next);
Console.WriteLine("カンマ区切りの連結結果:");
Console.WriteLine(result);
}
}
カンマ区切りの連結結果:
りんご, ばなな, みかん
Aggregate
は最初の要素をcurrent
に取り、次の要素をnext
に渡して結合を繰り返します。
区切り文字を自由に指定できるため、カスタムな連結処理に適しています。
インデントと改行を含む連結
複数行の文字列をインデント付きで連結したい場合もAggregate
で実現可能です。
例えば、各行の前にスペースを付けて改行を含む連結を行います。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<string> lines = new List<string> { "行1", "行2", "行3" };
string indented = lines.Aggregate("", (acc, line) => acc + " " + line + Environment.NewLine);
Console.WriteLine("インデントと改行を含む連結:");
Console.WriteLine(indented);
}
}
インデントと改行を含む連結:
行1
行2
行3
この例では、空文字から開始し、各行の前に2つのスペースを付けて改行を加えながら連結しています。
CountとGroupByで文字頻度
文字列や文字のシーケンスに対して、各文字の出現回数を集計するにはGroupBy
とCount
を組み合わせます。
これにより文字頻度の辞書を簡単に作成できます。
出現回数を辞書化
以下は文字列中の各文字の出現回数を辞書にまとめる例です。
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
string text = "abracadabra";
var frequency = text.GroupBy(ch => ch)
.ToDictionary(g => g.Key, g => g.Count());
Console.WriteLine("文字の出現回数:");
foreach (var kvp in frequency)
{
Console.WriteLine($"'{kvp.Key}': {kvp.Value}回");
}
}
}
文字の出現回数:
'a': 5回
'b': 2回
'r': 2回
'c': 1回
'd': 1回
GroupBy
で同じ文字ごとにグループ化し、Count
で各グループの要素数を数えています。
ToDictionary
でキー(文字)と値(出現回数)の辞書に変換しています。
MaxとMinで辞書順先頭末尾を取得
文字列のシーケンスや文字の集合から、辞書順で最も先頭や末尾に位置する要素を取得するにはMax
とMin
を使います。
using System;
using System.Linq;
class Program
{
static void Main()
{
string[] words = { "apple", "banana", "cherry", "date" };
string minWord = words.Min();
string maxWord = words.Max();
Console.WriteLine($"辞書順で最も先頭の単語: {minWord}");
Console.WriteLine($"辞書順で最も末尾の単語: {maxWord}");
}
}
辞書順で最も先頭の単語: apple
辞書順で最も末尾の単語: date
文字列のMin
は辞書順で最も小さい(先頭の)文字列を返し、Max
は最も大きい(末尾の)文字列を返します。
文字単位でも同様に使えます。
string input = "hello";
char minChar = input.Min();
char maxChar = input.Max();
Console.WriteLine($"最小文字: {minChar}");
Console.WriteLine($"最大文字: {maxChar}");
最小文字: e
最大文字: o
このように、Max
とMin
は文字列や文字のシーケンスの範囲を調べるのに便利です。
分割: チャンクとトークン化
Chunk拡張メソッドで一定長分割
.NET 6以降で利用可能なChunk
拡張メソッドは、シーケンスを指定したサイズのチャンク(塊)に分割します。
文字列を文字単位で分割し、一定長の部分列に分けたい場合に便利です。
using System;
using System.Linq;
class Program
{
static void Main()
{
string text = "123456789ABCDEF";
int chunkSize = 4;
var chunks = text.Chunk(chunkSize)
.Select(chars => new string(chars));
Console.WriteLine($"文字列を{chunkSize}文字ごとに分割:");
foreach (var chunk in chunks)
{
Console.WriteLine(chunk);
}
}
}
文字列を4文字ごとに分割:
1234
5678
9ABC
DEF
Chunk
は文字列をIEnumerable<char>
として扱い、指定したサイズごとに分割した配列のシーケンスを返します。
Select
で文字配列を文字列に変換しています。
GroupBy(index / n)パターン
Chunk
が使えない環境や、独自に分割したい場合は、Select
でインデックスを取得し、GroupBy
でグループ化する方法があります。
インデックスをn
で割った商をキーにしてグループ化することで、n
個ずつのチャンクに分割できます。
using System;
using System.Linq;
class Program
{
static void Main()
{
string text = "ABCDEFGHIJKL";
int chunkSize = 3;
var chunks = text.Select((ch, index) => new { ch, index })
.GroupBy(x => x.index / chunkSize)
.Select(g => new string(g.Select(x => x.ch).ToArray()));
Console.WriteLine($"GroupByで{chunkSize}文字ごとに分割:");
foreach (var chunk in chunks)
{
Console.WriteLine(chunk);
}
}
}
GroupByで3文字ごとに分割:
ABC
DEF
GHI
JKL
この方法はChunk
が使えない.NETのバージョンでも利用でき、柔軟に分割処理を実装できます。
SplitとLINQの組み合わせ
文字列の分割にはstring.Split
メソッドがよく使われますが、分割後の配列に対してLINQを組み合わせることで、さらに柔軟な処理が可能です。
空文字除去とTrim
Split
で区切った結果に空文字や不要な空白が含まれることがあります。
Where
で空文字を除去し、Select
で各要素の前後の空白をTrim
で削除する例です。
using System;
using System.Linq;
class Program
{
static void Main()
{
string csv = " apple, banana, , orange , , grape ";
var fruits = csv.Split(',')
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s));
Console.WriteLine("空文字除去とTrim後の要素:");
foreach (var fruit in fruits)
{
Console.WriteLine($"'{fruit}'");
}
}
}
空文字除去とTrim後の要素:
'apple'
'banana'
'orange'
'grape'
このように、Split
で分割した後にLINQで空文字を除去し、Trim
で余分な空白を取り除くことで、きれいなトークンのリストを得られます。
結合: Joinで関連付け
単語と翻訳表をJoin
LINQのJoin
メソッドは、2つのシーケンスを共通のキーで結合し、新しいシーケンスを作成します。
例えば、英単語のリストとその翻訳表を結びつけて、単語と対応する翻訳をペアで取得するケースに使えます。
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
var words = new List<string> { "apple", "banana", "cherry", "date" };
var translations = new List<(string English, string Japanese)>
{
("apple", "りんご"),
("banana", "ばなな"),
("cherry", "さくらんぼ"),
("fig", "いちじく")
};
var joined = words.Join(
translations,
word => word,
trans => trans.English,
(word, trans) => new { Word = word, Translation = trans.Japanese }
);
Console.WriteLine("単語と翻訳の結合結果:");
foreach (var item in joined)
{
Console.WriteLine($"{item.Word} => {item.Translation}");
}
}
}
単語と翻訳の結合結果:
apple => りんご
banana => ばなな
cherry => さくらんぼ
この例では、words
の各単語とtranslations
の英単語をキーにして結合し、対応する日本語訳を取得しています。
Join
は内部結合なので、両方に存在するキーだけが結果に含まれます。
GroupJoinでカテゴリ別連結
GroupJoin
は、左側のシーケンスの各要素に対して、右側のシーケンスの関連する複数の要素をグループ化して結合します。
カテゴリごとに複数のアイテムをまとめて関連付けたい場合に便利です。
以下は、商品カテゴリと商品リストをGroupJoin
で結合し、カテゴリごとに商品をまとめて表示する例です。
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
class Product
{
public string Name { get; set; }
public int CategoryId { get; set; }
}
static void Main()
{
var categories = new List<Category>
{
new Category { Id = 1, Name = "果物" },
new Category { Id = 2, Name = "野菜" }
};
var products = new List<Product>
{
new Product { Name = "りんご", CategoryId = 1 },
new Product { Name = "ばなな", CategoryId = 1 },
new Product { Name = "にんじん", CategoryId = 2 },
new Product { Name = "キャベツ", CategoryId = 2 }
};
var grouped = categories.GroupJoin(
products,
cat => cat.Id,
prod => prod.CategoryId,
(cat, prods) => new { Category = cat.Name, Products = prods.Select(p => p.Name) }
);
Console.WriteLine("カテゴリ別の商品一覧:");
foreach (var group in grouped)
{
Console.WriteLine($"{group.Category}:");
foreach (var product in group.Products)
{
Console.WriteLine($" - {product}");
}
}
}
}
カテゴリ別の商品一覧:
果物:
- りんご
- ばなな
野菜:
- にんじん
- キャベツ
この例では、categories
の各カテゴリに対して、products
の該当する商品をグループ化して関連付けています。
GroupJoin
は左外部結合のような動作をし、カテゴリに商品がない場合でもカテゴリは結果に含まれます。
パフォーマンス考慮
ToListを避けた遅延実行
LINQは基本的に遅延実行(Deferred Execution)を採用しており、クエリの実行は結果が実際に必要になったタイミングまで遅延されます。
これにより無駄な処理を避けられますが、ToList
やToArray
を呼び出すと即時実行(Immediate Execution)となり、すべての要素がメモリに展開されます。
例えば、文字列のフィルタリングを行い、その結果をすぐにリスト化するとメモリ消費が増え、パフォーマンスに影響を与えることがあります。
using System;
using System.Linq;
class Program
{
static void Main()
{
string input = "A1B2C3D4E5";
// 遅延実行のまま処理
var digitsQuery = input.Where(ch => char.IsDigit(ch));
Console.WriteLine("遅延実行の結果:");
foreach (var d in digitsQuery)
{
Console.Write(d + " ");
}
Console.WriteLine();
// 即時実行でリスト化
var digitsList = input.Where(ch => char.IsDigit(ch)).ToList();
Console.WriteLine("即時実行でリスト化した結果:");
foreach (var d in digitsList)
{
Console.Write(d + " ");
}
}
}
遅延実行の結果:
1 2 3 4 5
即時実行でリスト化した結果:
1 2 3 4 5
遅延実行のまま処理を続けることで、必要な分だけ処理し、メモリ使用量を抑えられます。
ToList
は便利ですが、不要な場合は避けるのがパフォーマンス向上のポイントです。
StringBuilder vs Aggregate
文字列の連結をLINQのAggregate
で行うことが多いですが、Aggregate
は内部的に文字列の連結を繰り返すため、文字列の長さが大きくなるとパフォーマンスが低下します。
これは文字列が不変(immutable)であるため、連結のたびに新しい文字列が生成されるからです。
一方、StringBuilder
は可変のバッファを使って効率的に文字列を連結できるため、大量の連結処理には適しています。
Aggregateでの連結例
using System;
using System.Linq;
class Program
{
static void Main()
{
var words = new[] { "Hello", "World", "from", "LINQ" };
string result = words.Aggregate((acc, next) => acc + " " + next);
Console.WriteLine("Aggregateで連結:");
Console.WriteLine(result);
}
}
Aggregateで連結:
Hello World from LINQ
StringBuilderでの連結例
using System;
using System.Text;
class Program
{
static void Main()
{
var words = new[] { "Hello", "World", "from", "LINQ" };
var sb = new StringBuilder();
foreach (var word in words)
{
if (sb.Length > 0) sb.Append(' ');
sb.Append(word);
}
Console.WriteLine("StringBuilderで連結:");
Console.WriteLine(sb.ToString());
}
}
StringBuilderで連結:
Hello World from LINQ
大量の文字列を連結する場合はStringBuilder
を使うほうが効率的です。
Aggregate
は簡潔ですが、パフォーマンスを重視する場面では注意が必要です。
Span<char>拡張とのハイブリッド
Span<char>
はC#のメモリ効率の良い文字列操作を可能にする構造体で、配列や文字列の部分範囲を安全かつ高速に扱えます。
LINQの遅延実行や柔軟性と組み合わせることで、パフォーマンスと可読性のバランスを取ることができます。
例えば、文字列の一部をSpan<char>
で切り出しつつ、LINQで条件抽出を行う例です。
using System;
using System.Linq; // Although not strictly needed for the corrected loop, it's fine to keep it if other LINQ operations are planned.
class Program
{
static void Main()
{
string input = "Hello123World456";
ReadOnlySpan<char> span = input.AsSpan();
Console.WriteLine("Span<char>とLINQの組み合わせで数字抽出 (修正版):");
// 数字だけを抽出(Spanを直接反復処理)
foreach (char ch in span)
{
if (char.IsDigit(ch))
{
Console.Write(ch + " ");
}
}
Console.WriteLine(); // 改行
}
}
Span<char>とLINQの組み合わせで数字抽出:
1 2 3 4 5 6
Span<char>
は配列や文字列のコピーを避けて部分的に操作できるため、メモリ割り当てを減らし高速化に寄与します。
LINQの柔軟なクエリ機能と組み合わせることで、効率的かつ表現力豊かな文字列処理が可能です。
ただし、Span<char>
はスタック上の構造体であり、非同期処理やクロージャー内での使用に制限があるため、使いどころを見極める必要があります。
エラー処理と安全性
Null文字列の扱い
LINQで文字列を扱う際、対象の文字列がnull
である場合は注意が必要です。
null
の文字列に対してLINQの拡張メソッドを呼び出すとNullReferenceException
が発生します。
安全に処理を行うためには、事前にnull
チェックを行うか、null
を空文字列に置き換える方法が一般的です。
using System;
using System.Linq;
class Program
{
static void Main()
{
string? input = null;
// nullチェックを行う例
if (!string.IsNullOrEmpty(input))
{
var letters = input.Where(ch => char.IsLetter(ch));
Console.WriteLine("文字数: " + letters.Count());
}
else
{
Console.WriteLine("入力文字列がnullまたは空です。");
}
// nullを空文字列に置き換えて処理する例
var safeInput = input ?? string.Empty;
var digits = safeInput.Where(ch => char.IsDigit(ch));
Console.WriteLine("数字の数: " + digits.Count());
}
}
入力文字列がnullまたは空です。
数字の数: 0
このように、null
の可能性がある文字列は必ずチェックするか、??
演算子で空文字列に置き換えてからLINQ処理を行うと安全です。
DefaultIfEmptyで空シーケンス回避
LINQのクエリ結果が空のシーケンスになる場合、後続の処理で例外や意図しない動作が起こることがあります。
DefaultIfEmpty
メソッドを使うと、空シーケンスの場合にデフォルト値を返すため、処理の安全性が向上します。
例えば、文字列から数字を抽出し、数字がなければ'0'
を返す例です。
using System;
using System.Linq;
class Program
{
static void Main()
{
string input = "abcde";
var digits = input.Where(ch => char.IsDigit(ch))
.DefaultIfEmpty('0');
Console.WriteLine("数字またはデフォルト値:");
foreach (var ch in digits)
{
Console.Write(ch + " ");
}
}
}
数字またはデフォルト値:
0
この例では、数字が1つも見つからなかったため、DefaultIfEmpty
によって'0'
が返されています。
これにより、空シーケンスによる問題を回避できます。
TryParse系とSelectで数値変換
文字列の数値変換では、int.Parse
などの直接変換は例外を投げる可能性があるため、int.TryParse
を使うのが安全です。
LINQのSelect
と組み合わせて、変換に成功した値だけを抽出する方法を紹介します。
using System;
using System.Linq;
class Program
{
static void Main()
{
string[] inputs = { "10", "abc", "20", "30x", "40" };
var numbers = inputs.Select(s =>
{
bool success = int.TryParse(s, out int value);
return new { success, value };
})
.Where(x => x.success)
.Select(x => x.value);
Console.WriteLine("変換成功した数値:");
foreach (var num in numbers)
{
Console.WriteLine(num);
}
}
}
変換成功した数値:
10
20
40
このコードでは、Select
でTryParse
の結果を匿名型で保持し、Where
で成功したものだけをフィルタリングしています。
これにより、無効な文字列による例外を防ぎつつ安全に数値変換ができます。
実用サンプル集
CSVパースと再生成
CSV(カンマ区切り値)形式の文字列をLINQでパースし、必要に応じて加工して再生成する例です。
ここでは、CSVの各行を分割し、特定の列だけを抽出して新しいCSVを作成します。
using System;
using System.Linq;
class Program
{
static void Main()
{
string csvData =
@"名前,年齢,職業
山田太郎,30,エンジニア
鈴木花子,25,デザイナー
田中一郎,40,マネージャー";
// CSVを行ごとに分割し、ヘッダーとデータに分ける
var lines = csvData.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
var header = lines.First().Split(',');
var dataLines = lines.Skip(1);
// 年齢が30以上の人の名前と職業だけを抽出
var filtered = dataLines.Select(line => line.Split(','))
.Where(fields => int.TryParse(fields[1], out int age) && age >= 30)
.Select(fields => new { Name = fields[0], Job = fields[2] });
// 新しいCSVを生成
var newCsv = "名前,職業\n" +
string.Join("\n", filtered.Select(item => $"{item.Name},{item.Job}"));
Console.WriteLine("30歳以上の人の名前と職業:");
Console.WriteLine(newCsv);
}
}
30歳以上の人の名前と職業:
名前,職業
山田太郎,エンジニア
田中一郎,マネージャー
この例では、Split
で行と列に分割し、Where
で条件を指定、Select
で必要な列を抽出しています。
最後にstring.Join
で新しいCSV形式の文字列を作成しています。
ログファイルのエラーメッセージ抽出
ログファイルのテキストからエラーメッセージだけを抽出する例です。
LINQと正規表現を組み合わせて、エラーレベルのログ行をフィルタリングします。
using System;
using System.Linq;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
string log =
@"INFO 2024-06-01 10:00:00 処理開始
ERROR 2024-06-01 10:01:00 ファイルが見つかりません
WARN 2024-06-01 10:02:00 メモリ使用率が高い
ERROR 2024-06-01 10:03:00 ネットワーク接続失敗";
var errorLines = log.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Where(line => Regex.IsMatch(line, @"^ERROR"));
Console.WriteLine("エラーメッセージ抽出:");
foreach (var line in errorLines)
{
Console.WriteLine(line);
}
}
}
エラーメッセージ抽出:
ERROR 2024-06-01 10:01:00 ファイルが見つかりません
ERROR 2024-06-01 10:03:00 ネットワーク接続失敗
Split
でログを行単位に分割し、Regex.IsMatch
でERROR
で始まる行だけを抽出しています。
これにより、エラーメッセージだけを効率的に取り出せます。
ユーザ入力サニタイズ
ユーザからの入力文字列をLINQでサニタイズ(不要な文字の除去や変換)する例です。
ここでは、英数字と一部の記号だけを許可し、それ以外の文字を除去します。
using System;
using System.Linq;
class Program
{
static void Main()
{
string userInput = "Hello! こんにちは123 @#¥%";
// 許可する文字集合(英数字と一部記号)
char[] allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#".ToCharArray();
var sanitized = new string(userInput.Where(ch => allowedChars.Contains(ch)).ToArray());
Console.WriteLine("サニタイズ後の入力:");
Console.WriteLine(sanitized);
}
}
サニタイズ後の入力:
Hello123@#
この例では、Where
で許可文字だけを抽出し、new string
で再構築しています。
LINQを使うことで簡潔にサニタイズ処理が実装できます。
よくある落とし穴
反復評価による多重実行
LINQのクエリは遅延実行が基本であり、クエリの結果は列挙されるたびに再評価されます。
これにより、同じクエリを複数回列挙すると、意図せず同じ処理が何度も実行されてしまうことがあります。
特に副作用のある処理や重い計算を含む場合はパフォーマンス低下や予期しない動作の原因になります。
using System;
using System.Linq;
class Program
{
static int counter = 0;
static void Main()
{
var numbers = Enumerable.Range(1, 3).Select(n =>
{
counter++;
Console.WriteLine($"処理中: {n}");
return n * 2;
});
Console.WriteLine("1回目の列挙:");
foreach (var num in numbers)
{
Console.WriteLine(num);
}
Console.WriteLine("2回目の列挙:");
foreach (var num in numbers)
{
Console.WriteLine(num);
}
Console.WriteLine($"処理回数: {counter}");
}
}
1回目の列挙:
処理中: 1
2
処理中: 2
4
処理中: 3
6
2回目の列挙:
処理中: 1
2
処理中: 2
4
処理中: 3
6
処理回数: 6
この例では、numbers
のクエリが2回列挙されるため、Select
内の処理が2回ずつ実行されています。
これを避けるには、ToList()
やToArray()
で結果をキャッシュしておく方法があります。
Mutableオブジェクトとの相互作用
LINQはシーケンスの要素を参照で扱うため、ミュータブル(変更可能)なオブジェクトを操作すると、クエリの結果が予期せぬ形で変化することがあります。
特に、クエリの実行後にオブジェクトの状態を変更すると、結果に影響を与えます。
using System;
using System.Collections.Generic;
using System.Linq;
class Item
{
public int Value { get; set; }
}
class Program
{
static void Main()
{
var items = new List<Item>
{
new Item { Value = 1 },
new Item { Value = 2 },
new Item { Value = 3 }
};
var query = items.Where(item => item.Value > 1);
// クエリ実行前に値を変更
items[0].Value = 5;
Console.WriteLine("クエリ結果:");
foreach (var item in query)
{
Console.WriteLine(item.Value);
}
}
}
クエリ結果:
5
2
3
この例では、items[0]
のValue
を変更したため、Where
の条件に合致する要素が変わっています。
ミュータブルなオブジェクトを扱う場合は、クエリの実行タイミングやオブジェクトの状態管理に注意が必要です。
拡張メソッドを自作する
フルワード検索ContainsAll
複数のキーワードすべてを含むかどうかを判定する拡張メソッドContainsAll
を作成します。
文字列が指定したすべての単語を含んでいるかをチェックする際に便利です。
using System;
using System.Linq;
public static class StringExtensions
{
// 文字列がすべてのキーワードを含むか判定する拡張メソッド
public static bool ContainsAll(this string source, params string[] keywords)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (keywords == null) throw new ArgumentNullException(nameof(keywords));
return keywords.All(keyword => !string.IsNullOrEmpty(keyword) && source.Contains(keyword));
}
}
class Program
{
static void Main()
{
string text = "LINQはC#の強力なクエリ機能です";
bool result1 = text.ContainsAll("LINQ", "C#");
bool result2 = text.ContainsAll("LINQ", "Java");
Console.WriteLine($"'LINQ'と'C#'を含むか: {result1}");
Console.WriteLine($"'LINQ'と'Java'を含むか: {result2}");
}
}
'LINQ'と'C#'を含むか: True
'LINQ'と'Java'を含むか: False
この拡張メソッドは、params
で複数のキーワードを受け取り、すべてのキーワードがsource
に含まれているかをAll
で判定しています。
空文字やnull
は無視せず例外を投げる設計です。
CamelCaseスプリッタ
CamelCaseやPascalCaseの文字列を単語ごとに分割する拡張メソッドを作成します。
大文字で始まる単語の境界を検出し、単語のリストを返します。
using System;
using System.Collections.Generic;
using System.Text;
public static class StringExtensions
{
public static IEnumerable<string> SplitCamelCase(this string source)
{
if (string.IsNullOrEmpty(source))
yield break;
var sb = new StringBuilder();
foreach (char ch in source)
{
if (char.IsUpper(ch) && sb.Length > 0)
{
yield return sb.ToString();
sb.Clear();
}
sb.Append(ch);
}
yield return sb.ToString();
}
}
class Program
{
static void Main()
{
string camelCase = "ThisIsCamelCaseExample";
var words = camelCase.SplitCamelCase();
Console.WriteLine("CamelCase分割結果:");
foreach (var word in words)
{
Console.WriteLine(word);
}
}
}
CamelCase分割結果:
This
Is
Camel
Case
Example
このメソッドは文字列を1文字ずつ走査し、大文字が現れたら現在の単語を区切ってyield return
します。
最後の単語も忘れずに返します。
左パディング改良版PadLeftLinq
標準のPadLeft
は文字列の左側に指定した文字を追加して指定長にしますが、LINQを使って同様の機能を自作し、柔軟に拡張できる例です。
using System;
using System.Linq;
public static class StringExtensions
{
public static string PadLeftLinq(this string source, int totalWidth, char paddingChar = ' ')
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (totalWidth <= source.Length) return source;
int padCount = totalWidth - source.Length;
var padding = Enumerable.Repeat(paddingChar, padCount);
return string.Concat(padding) + source;
}
}
class Program
{
static void Main()
{
string text = "123";
string padded = text.PadLeftLinq(6, '0');
Console.WriteLine($"元の文字列: '{text}'");
Console.WriteLine($"パディング後: '{padded}'");
}
}
元の文字列: '123'
パディング後: '000123'
この拡張メソッドは、Enumerable.Repeat
で指定した数だけパディング文字を生成し、string.Concat
で連結してから元の文字列に付加しています。
標準のPadLeft
と同様の動作をLINQで実装していますが、必要に応じてさらにカスタマイズ可能です。
C#バージョン別機能差異
LINQ to Objects限定機能
LINQは大きく分けて「LINQ to Objects」「LINQ to SQL」「LINQ to XML」など複数の実装がありますが、ここで紹介する機能は主に「LINQ to Objects」、つまりメモリ上のコレクションに対して動作するLINQの拡張メソッドに限定されます。
LINQ to ObjectsはIEnumerable<T>
を対象にしており、配列やリスト、文字列などのシーケンスに対して柔軟なクエリを実行できます。
例えば、Where
やSelect
、GroupBy
、OrderBy
などの標準的なメソッドはLINQ to Objectsで利用可能です。
一方、LINQ to SQLやLINQ to Entitiesでは、SQLに変換可能なメソッドしか使えず、ToList
やToArray
などの即時実行メソッドや、Chunk
のような新しい拡張は使えない場合があります。
また、LINQ to Objects限定の機能としては、以下のようなものがあります。
Chunk
メソッド
.NET 6で導入された、シーケンスを指定したサイズのチャンクに分割するメソッド。
メモリ上のシーケンスにのみ適用可能です。
Zip
の拡張
複数のシーケンスを要素ごとに結合する機能で、LINQ to Objectsで特に活用されます。
Aggregate
の多様なオーバーロード
複雑な集約処理を柔軟に記述可能です。
これらはLINQ to Objectsの強みであり、C#のバージョンや.NETのバージョンによって利用可能な機能が異なるため、開発環境に応じて使い分けが必要です。
.NET 6のChunk拡張
.NET 6で新たに追加されたChunk
拡張メソッドは、IEnumerable<T>
のシーケンスを指定したサイズのチャンク(小分割)に分割する機能です。
これにより、大きなシーケンスを扱う際に一定サイズごとに処理を分割でき、コードがシンプルかつ効率的になります。
using System;
using System.Linq;
class Program
{
static void Main()
{
var numbers = Enumerable.Range(1, 10);
int chunkSize = 3;
var chunks = numbers.Chunk(chunkSize);
Console.WriteLine($"{chunkSize}個ずつのチャンクに分割:");
foreach (var chunk in chunks)
{
Console.WriteLine(string.Join(", ", chunk));
}
}
}
3個ずつのチャンクに分割:
1, 2, 3
4, 5, 6
7, 8, 9
10
Chunk
は内部で効率的にバッファリングし、最後のチャンクは要素数が不足していても問題なく返します。
これまではGroupBy
とインデックス計算で代用していましたが、Chunk
の導入でコードがより直感的になりました。
.NET 8のメモリ効率改善
.NET 8では、LINQのパフォーマンスとメモリ効率がさらに改善されています。
特に文字列や配列の操作において、Span<T>
やMemory<T>
を活用した内部実装の最適化が進み、不要なメモリ割り当てを削減しています。
具体的な改善点の例は以下の通りです。
Where
やSelect
の内部処理の最適化
遅延実行時のクロージャー生成やデリゲート呼び出しのオーバーヘッドが減少し、より高速に処理が行われます。
Chunk
やTake
、Skip
のメモリ割り当て削減
これらのメソッドがSpan<T>
を活用し、配列のコピーを最小限に抑えています。
string.Concat
の高速化
文字列連結の内部処理が改善され、大量の文字列連結でも効率的に動作します。
これらの改善により、LINQを使った文字列操作やコレクション処理がより高速かつ低メモリで実行可能になりました。
最新の.NETランタイムを利用することで、パフォーマンス面での恩恵を受けられます。
これらのバージョン別の機能差異を理解し、開発環境に合わせて適切なLINQ機能を選択することが、効率的で保守性の高いコードを書くポイントです。
まとめ
この記事では、C#のLINQを活用した文字列操作の基本から応用まで幅広く解説しました。
文字列をシーケンスとして扱い、抽出・変形・連結・分割・結合など多彩な操作を簡潔に実装する方法がわかります。
パフォーマンスやエラー処理の注意点、拡張メソッドの自作例、バージョンごとの機能差異も押さえられるため、実務での効率的な文字列処理に役立ちます。