
前置き
Linqに関してよく忘れるので備忘録です。
Linqとは
C# の LINQ (Language Integrated Query) には、さまざまな種類のメソッドがあります。これらのメソッドは、コレクション (配列、リストなど) に対してクエリを実行し、データのフィルタリング、並べ替え、変換、グループ化などの操作を行うために使用されます。
LINQ のメソッドは、大きく分けて以下のカテゴリに分類できます。
フィルタリング:
Where: 指定された条件に基づいて要素をフィルタリングします。
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// クエリ構文
var evenNumbersQuery = from num in numbers
where num % 2 == 0
select num;
// メソッド構文
var evenNumbersMethod = numbers.Where(num => num % 2 == 0);
Console.WriteLine("偶数 (クエリ構文): " + string.Join(", ", evenNumbersQuery));
Console.WriteLine("偶数 (メソッド構文): " + string.Join(", ", evenNumbersMethod));
実行結果
偶数 (クエリ構文): 2, 4, 6, 8, 10
偶数 (メソッド構文): 2, 4, 6, 8, 10
OfType: 指定された型である要素のみを選択します。
List<object> mixedList = new List<object> { 1, "hello", 2.5, 3, "world" };
// クエリ構文
var integersQuery = from item in mixedList
where item is int
select item;
// メソッド構文
var integersMethod = mixedList.OfType<int>();
Console.WriteLine("整数 (クエリ構文): " + string.Join(", ", integersQuery));
Console.WriteLine("整数 (メソッド構文): " + string.Join(", ", integersMethod));
実行結果
整数 (クエリ構文): 1, 3
整数 (メソッド構文): 1, 3
射影:
Select: 各要素を新しい形式に変換します。
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
// クエリ構文
var nameLengthsQuery = from name in names
select name.Length;
// メソッド構文
var nameLengthsMethod = names.Select(name => name.Length);
Console.WriteLine("名前の長さ (クエリ構文): " + string.Join(", ", nameLengthsQuery));
Console.WriteLine("名前の長さ (メソッド構文): " + string.Join(", ", nameLengthsMethod));
実行結果
名前の長さ (クエリ構文): 5, 3, 7
名前の長さ (メソッド構文): 5, 3, 7
SelectMany: 各要素からコレクションを抽出し、それらを 1 つのフラットなコレクションに結合します。
List<List<int>> listOfLists = new List<List<int>> {
new List<int> { 1, 2 },
new List<int> { 3, 4, 5 },
new List<int> { 6 }
};
// クエリ構文
var allNumbersQuery = from list in listOfLists
from num in list
select num;
// メソッド構文
var allNumbersMethod = listOfLists.SelectMany(list => list);
Console.WriteLine("すべての数値 (クエリ構文): " + string.Join(", ", allNumbersQuery));
Console.WriteLine("すべての数値 (メソッド構文): " + string.Join(", ", allNumbersMethod));
実行結果
すべての数値 (クエリ構文): 1, 2, 3, 4, 5, 6
すべての数値 (メソッド構文): 1, 2, 3, 4, 5, 6
3. 並べ替え:
- OrderBy: 要素を昇順に並べ替えます。
- OrderByDescending: 要素を降順に並べ替えます。
- ThenBy: 最初の並べ替えの後に、さらに昇順で並べ替えます。
- ThenByDescending: 最初の並べ替えの後に、さらに降順で並べ替えます。
- Reverse: 要素の順序を反転します。
List<string> fruits = new List<string> { "Banana", "Apple", "Orange", "Grape" };
// クエリ構文 (OrderBy)
var sortedFruitsAscQuery = from fruit in fruits
orderby fruit
select fruit;
// メソッド構文 (OrderByDescending)
var sortedFruitsDescMethod = fruits.OrderByDescending(fruit => fruit);
Console.WriteLine("昇順 (クエリ構文): " + string.Join(", ", sortedFruitsAscQuery));
Console.WriteLine("降順 (メソッド構文): " + string.Join(", ", sortedFruitsDescMethod));
List<Person> people = new List<Person> {
new Person { FirstName = "太郎", LastName = "山田", Age = 30 },
new Person { FirstName = "花子", LastName = "佐藤", Age = 25 },
new Person { FirstName = "健太", LastName = "山田", Age = 20 }
};
// クエリ構文 (OrderBy, ThenBy)
var sortedPeopleQuery = from person in people
orderby person.LastName, person.FirstName descending
select person;
// メソッド構文 (OrderBy, ThenBy)
var sortedPeopleMethod = people.OrderBy(p => p.LastName).ThenByDescending(p => p.FirstName);
Console.WriteLine("複合ソート (クエリ構文):");
foreach (var person in sortedPeopleQuery)
{
Console.WriteLine($" {person.LastName} {person.FirstName} ({person.Age})");
}
Console.WriteLine("複合ソート (メソッド構文):");
foreach (var person in sortedPeopleMethod)
{
Console.WriteLine($" {person.LastName} {person.FirstName} ({person.Age})");
}
実行結果
昇順 (クエリ構文): Apple, Banana, Grape, Orange
降順 (メソッド構文): Orange, Grape, Banana, Apple
複合ソート (クエリ構文):
佐藤 花子 (25)
佐藤 優子 (35)
山田 健太 (20)
山田 太郎 (30)
複合ソート (メソッド構文):
佐藤 花子 (25)
佐藤 優子 (35)
山田 健太 (20)
山田 太郎 (30)
4. グループ化:
- GroupBy: 共通のキーに基づいて要素をグループ化します。
List<Person> people = new List<Person> {
new Person { FirstName = "太郎", LastName = "山田", Age = 30 },
new Person { FirstName = "花子", LastName = "佐藤", Age = 25 },
new Person { FirstName = "健太", LastName = "山田", Age = 20 },
new Person { FirstName = "優子", LastName = "佐藤", Age = 35 }
};
// クエリ構文
var groupedByLastNameQuery = from person in people
group person by person.LastName into g
select new { LastName = g.Key, People = g };
// メソッド構文
var groupedByLastNameMethod = people.GroupBy(person => person.LastName)
.Select(g => new { LastName = g.Key, People = g });
Console.WriteLine("グループ化 (クエリ構文):");
foreach (var group in groupedByLastNameQuery)
{
Console.WriteLine($" 姓: {group.LastName}");
foreach (var person in group.People)
{
Console.WriteLine($" - {person.FirstName} {person.LastName} ({person.Age})");
}
}
Console.WriteLine("グループ化 (メソッド構文):");
foreach (var group in groupedByLastNameMethod)
{
Console.WriteLine($" 姓: {group.LastName}");
foreach (var person in group.People)
{
Console.WriteLine($" - {person.FirstName} {person.LastName} ({person.Age})");
}
}
実行結果
グループ化 (クエリ構文):
姓: 山田
- 太郎 山田 (30)
- 健太 山田 (20)
姓: 佐藤
- 花子 佐藤 (25)
- 優子 佐藤 (35)
グループ化 (メソッド構文):
姓: 山田
- 太郎 山田 (30)
- 健太 山田 (20)
姓: 佐藤
- 花子 佐藤 (25)
- 優子 佐藤 (35)
5. 結合:
Join: 2 つのコレクションの関連する要素を結合します。
List<Product> products = new List<Product> {
new Product { ProductId = 1, Name = "Laptop" },
new Product { ProductId = 2, Name = "Mouse" },
new Product { ProductId = 3, Name = "Keyboard" }
};
List<Order> orders = new List<Order> {
new Order { OrderId = 101, ProductId = 1, Quantity = 1 },
new Order { OrderId = 102, ProductId = 2, Quantity = 2 },
new Order { OrderId = 103, ProductId = 1, Quantity = 3 }
};
// クエリ構文
var productOrdersQuery = from product in products
join order in orders on product.ProductId equals order.ProductId
select new { product.Name, order.OrderId, order.Quantity };
// メソッド構文
var productOrdersMethod = products.Join(orders,
product => product.ProductId,
order => order.ProductId,
(product, order) => new { product.Name, order.OrderId, order.Quantity });
Console.WriteLine("結合 (クエリ構文):");
foreach (var po in productOrdersQuery)
{
Console.WriteLine($" 製品名: {po.Name}, 注文ID: {po.OrderId}, 数量: {po.Quantity}");
}
Console.WriteLine("結合 (メソッド構文):");
foreach (var po in productOrdersMethod)
{
Console.WriteLine($" 製品名: {po.Name}, 注文ID: {po.OrderId}, 数量: {po.Quantity}");
}
実行結果
結合 (クエリ構文):
製品名: Laptop, 注文ID: 101, 数量: 1
製品名: Mouse, 注文ID: 102, 数量: 2
製品名: Laptop, 注文ID: 103, 数量: 3
結合 (メソッド構文):
製品名: Laptop, 注文ID: 101, 数量: 1
製品名: Mouse, 注文ID: 102, 数量: 2
製品名: Laptop, 注文ID: 103, 数量: 3
集計:
- Count: コレクション内の要素数を返します。
- Sum: 数値コレクションの要素の合計を計算します。
- Min: 数値コレクションの最小値を返します。
- Max: 数値コレクションの最大値を返します。
- Average: 数値コレクションの要素の平均値を計算します。
List<int> numbers = new List<int> { 1, 5, 2, 8, 3 };
int count = numbers.Count();
int sum = numbers.Sum();
int min = numbers.Min();
int max = numbers.Max();
double average = numbers.Average();
Console.WriteLine($"要素数: {count}");
Console.WriteLine($"合計: {sum}");
Console.WriteLine($"最小値: {min}");
Console.WriteLine($"最大値: {max}");
Console.WriteLine($"平均値: {average}");
実行結果
要素数: 5
合計: 19
最小値: 1
最大値: 8
平均値: 3.8
要素の取得:
- First: シーケンスの最初の要素を返します。
- FirstOrDefault: シーケンスの最初の要素を返します。シーケンスが空の場合は既定値を返します。
- Last: シーケンスの最後の要素を返します。
- LastOrDefault: シーケンスの最後の要素を返します。シーケンスが空の場合は既定値を返します。
- Single: シーケンスの唯一の要素を返します。シーケンスが空であるか、複数の要素が含まれている場合は例外をスローします。
- SingleOrDefault: シーケンスの唯一の要素を返します。シーケンスが空の場合は既定値を返します。複数の要素が含まれている場合は例外をスローします。
- ElementAt: 指定されたインデックスにある要素を返します。
- ElementAtOrDefault: 指定されたインデックスにある要素を返します。インデックスが範囲外の場合は既定値を返します。
List<string> colors = new List<string> { "Red", "Green", "Blue" };
string firstColor = colors.First();
string lastColor = colors.Last();
string secondColor = colors.ElementAt(1);
string defaultColor = colors.FirstOrDefault(c => c.StartsWith("Y")); // 条件に一致しない場合は null
Console.WriteLine($"最初の色: {firstColor}");
Console.WriteLine($"最後の色: {lastColor}");
Console.WriteLine($"2番目の色: {secondColor}");
Console.WriteLine($"デフォルトの色: {defaultColor ?? "(null)"}");
実行結果
最初の色: Red
最後の色: Blue
2番目の色: Green
デフォルトの色: (null)
集合操作:
Distinct: 重複する要素を削除します。
Union: 2 つのコレクションの和集合を生成します。
Intersect: 2 つのコレクションの積集合を生成します。
Except: 最初のコレクションにあり、2 番目のコレクションにない要素を返します。
List<int> list1 = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
List<int> list2 = new List<int> { 4, 5, 6, 7 };
var distinctNumbers = list1.Distinct();
var unionNumbers = list1.Union(list2);
var intersectNumbers = list1.Intersect(list2);
var exceptNumbers = list1.Except(list2);
Console.WriteLine("重複なし:string.Join(",",distinctNumbers)");Console.WriteLine("和集合: {string.Join(", ", unionNumbers)}");
Console.WriteLine("積集合:string.Join(",",intersectNumbers)");Console.WriteLine("差集合 (list1 - list2): {string.Join(", ", exceptNumbers)}");
実行結果
重複なし: 1, 2, 3, 4, 5
和集合: 1, 2, 3, 4, 5, 6, 7
積集合: 4, 5
差集合 (list1 - list2): 1, 2, 3
これらの実行結果は、それぞれの LINQ メソッドが期待通りに動作していることを示しています。状況に応じてクエリ構文とメソッド構文を使い分けることで、より可読性の高い、効率的なコードを書くことができます。