C#のSelectとは?LINQでリスト・配列を変換する使い方とSelectManyとの違いをサンプルコードで解説
はじめに
C#でリストや配列の中身を別の形に変換したいときによく使うのが、LINQのSelectです。
たとえば、次のような処理をしたい場面があります。
C#var numbers = new List<int> { 1, 2, 3 };
var doubled = numbers.Select(x => x * 2);
このコードでは、1, 2, 3という数値の一覧を、2, 4, 6という別の一覧に変換しています。
SelectはC#のLINQで非常によく使われるメソッドですが、初心者のうちは「foreachと何が違うのか」「Whereとどう違うのか」「SelectManyとは何が違うのか」で迷いやすいポイントでもあります。
この記事では、C#のSelectについて、リスト・配列・オブジェクト一覧での使い方から、SelectManyとの違い、よくあるエラーや注意点までサンプルコード付きで解説します。
1. C#のSelectとは?LINQで要素を別の形に変換するメソッド
1-1. Selectはコレクションの各要素を1件ずつ変換するLINQメソッド
C#のSelectは、LINQで提供されているメソッドのひとつです。
List<T>や配列などのコレクションに対して、各要素を1件ずつ取り出し、別の値や別の型に変換できます。
たとえば、数値のリストを2倍にする場合は次のように書きます。
C#var numbers = new List<int> { 1, 2, 3, 4, 5 };
var result = numbers.Select(x => x * 2);
foreach (var value in result)
{
Console.WriteLine(value);
}
実行結果は次のとおりです。
C#2
4
6
8
10
Selectは「元のコレクションの各要素を、指定したルールで変換する」と考えると理解しやすいです。
このとき、元のnumbers自体が変更されるわけではありません。Selectは元のデータを加工した新しい結果を返します。
1-2. Selectでできること:値の加工・型変換・プロパティ抽出・新しいオブジェクト作成
Selectでは、単純な数値の計算だけでなく、さまざまな変換ができます。
代表的な使い方は次のとおりです。
C#var numbers = new List<int> { 1, 2, 3 };
var doubled = numbers.Select(x => x * 2);
これは値の加工です。
文字列を加工することもできます。
C#var names = new List<string> { "tanaka", "sato", "suzuki" };
var upperNames = names.Select(name => name.ToUpper());
オブジェクトの一覧から特定のプロパティだけを取り出すこともできます。
C#var users = new List<User>
{
new User { Id = 1, Name = "田中" },
new User { Id = 2, Name = "佐藤" }
};
var names = users.Select(user => user.Name);
さらに、既存のオブジェクトから新しいオブジェクトを作ることもできます。
C#var userDtos = users.Select(user => new UserDto
{
Id = user.Id,
DisplayName = user.Name
});
このように、Selectは「コレクションの中身を別の形に変える」ためのメソッドです。
1-3. Selectを使う場面と使わない場面
Selectを使うのに向いているのは、次のような場面です。
C#var prices = new List<int> { 100, 200, 300 };
var taxIncludedPrices = prices.Select(price => price * 1.1);
このように、各要素を変換して新しい一覧を作る処理にはSelectが向いています。
一方で、単に条件に合う要素だけを取り出したい場合はSelectではなくWhereを使います。
C#var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(x => x % 2 == 0);
また、各要素に対してログ出力や外部API呼び出しのような副作用のある処理を行いたいだけなら、foreachを使った方が読みやすい場合があります。
C#foreach (var name in names)
{
Console.WriteLine(name);
}
Selectは「変換して結果を作る」ためのものです。単に処理を実行したいだけなら、foreachの方が適していることもあります。
2. Selectの基本構文と使い方
2-1. Selectの基本構文
Selectの基本構文は次のとおりです。
C#var result = collection.Select(element => 変換後の値);
たとえば、整数のリストを文字列に変換する場合は次のように書きます。
C#var numbers = new List<int> { 1, 2, 3 };
var strings = numbers.Select(x => x.ToString());
このコードでは、int型の各要素をstring型に変換しています。
Selectの中で使っているx => x.ToString()はラムダ式です。xはコレクション内の各要素を表し、=>の右側に変換後の値を書きます。
2-2. ラムダ式を使ったSelectの書き方
Selectではラムダ式を使うのが一般的です。
C#var numbers = new List<int> { 10, 20, 30 };
var result = numbers.Select(number => number + 5);
この場合、numberにはリストの要素が1つずつ入ります。
処理の流れは次のようなイメージです。
C#10 => 15
20 => 25
30 => 35
ラムダ式の変数名は自由に決められます。
C#var result1 = numbers.Select(x => x + 5);
var result2 = numbers.Select(number => number + 5);
var result3 = numbers.Select(value => value + 5);
どれも同じ意味です。
ただし、実務では意味がわかりやすい名前を付けると可読性が上がります。
C#var prices = new List<int> { 100, 200, 300 };
var taxIncludedPrices = prices.Select(price => price * 1.1);
xでも動きますが、priceと書いた方が「価格を変換している」と読み取りやすくなります。
2-3. メソッド構文とクエリ構文の違い
LINQには、メソッド構文とクエリ構文があります。
メソッド構文でSelectを書くと次のようになります。
C#var names = users.Select(user => user.Name);
クエリ構文で書くと次のようになります。
C#var names =
from user in users
select user.Name;
どちらも結果は同じです。
C#では、LINQのクエリ構文のselectは、内部的にはSelectメソッドに変換されます。そのため、単純な変換であればメソッド構文を使うことが多いです。
複数の条件や結合がある場合は、クエリ構文の方が読みやすいこともあります。
C#var result =
from user in users
where user.Age >= 20
orderby user.Name
select user.Name;
同じ処理をメソッド構文で書くと次のようになります。
C#var result = users
.Where(user => user.Age >= 20)
.OrderBy(user => user.Name)
.Select(user => user.Name);
実務では、メソッド構文の方がよく使われます。特にWhere、OrderBy、Selectをチェーンして書くパターンは頻出です。
2-4. Selectの戻り値はIEnumerable<T>になる
Selectの戻り値は、基本的にIEnumerable<T>です。
たとえば、次のコードを見てみましょう。
C#var numbers = new List<int> { 1, 2, 3 };
IEnumerable<int> result = numbers.Select(x => x * 2);
numbersはList<int>ですが、Selectの結果はIEnumerable<int>になります。
そのため、List<int>として使いたい場合はToList()を呼び出します。
C#List<int> result = numbers
.Select(x => x * 2)
.ToList();
配列として使いたい場合はToArray()を使います。
C#int[] result = numbers
.Select(x => x * 2)
.ToArray();
Selectの戻り値がIEnumerable<T>であることは、C# LINQを理解するうえで重要です。
3. ListでSelectを使う基本サンプル
3-1. List<int>の各要素を2倍に変換する
List<int>でSelectを使う基本例です。
C#var numbers = new List<int> { 1, 2, 3, 4, 5 };
var doubledNumbers = numbers.Select(number => number * 2);
foreach (var number in doubledNumbers)
{
Console.WriteLine(number);
}
実行結果は次のようになります。
C#2
4
6
8
10
Selectの中でnumber => number * 2と書くことで、各要素を2倍に変換しています。
元のリストは変更されません。
C#foreach (var number in numbers)
{
Console.WriteLine(number);
}
この場合、元のnumbersは次のままです。
C#1
2
3
4
5
Selectは元のコレクションを書き換えるのではなく、変換結果を返すメソッドです。
3-2. List<string>の文字列を加工する
文字列のリストでもSelectを使えます。
C#var names = new List<string> { "tanaka", "sato", "suzuki" };
var upperNames = names.Select(name => name.ToUpper());
foreach (var name in upperNames)
{
Console.WriteLine(name);
}
実行結果は次のようになります。
C#TANAKA
SATO
SUZUKI
文字列の前後に文字を追加することもできます。
C#var names = new List<string> { "田中", "佐藤", "鈴木" };
var displayNames = names.Select(name => $"ユーザー名:{name}");
foreach (var name in displayNames)
{
Console.WriteLine(name);
}
実行結果は次のとおりです。
C#ユーザー名:田中
ユーザー名:佐藤
ユーザー名:鈴木
Selectを使うと、文字列の整形や表示用データの作成も簡潔に書けます。
3-3. Listから特定のプロパティだけを取り出す
オブジェクトのリストから、特定のプロパティだけを取り出す場面でもSelectはよく使われます。
C#public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}
次のようなユーザー一覧があるとします。
C#var users = new List<User>
{
new User { Id = 1, Name = "田中", Age = 25 },
new User { Id = 2, Name = "佐藤", Age = 30 },
new User { Id = 3, Name = "鈴木", Age = 28 }
};
名前だけを取り出すには、次のように書きます。
C#var names = users.Select(user => user.Name);
foreach (var name in names)
{
Console.WriteLine(name);
}
実行結果は次のとおりです。
C#田中
佐藤
鈴木
IDだけを取り出す場合は次のように書けます。
C#var ids = users.Select(user => user.Id);
このように、Selectはオブジェクト一覧から必要な項目だけを抽出する処理に向いています。
3-4. Selectの結果をToListでListに変換する
Selectの結果をList<T>として扱いたい場合は、ToList()を使います。
C#var numbers = new List<int> { 1, 2, 3 };
List<int> doubledNumbers = numbers
.Select(number => number * 2)
.ToList();
ToList()を付けない場合、戻り値はIEnumerable<int>です。
C#IEnumerable<int> doubledNumbers = numbers.Select(number => number * 2);
実務では、次のような場面でToList()を使うことが多いです。
C#var names = users
.Select(user => user.Name)
.ToList();
List<string>として変数に代入したい場合や、後続でAddなどのList固有のメソッドを使いたい場合はToList()が必要です。
C#var names = users
.Select(user => user.Name)
.ToList();
names.Add("山田");
ただし、単にforeachで回すだけなら、必ずしもToList()は必要ありません。
C#var names = users.Select(user => user.Name);
foreach (var name in names)
{
Console.WriteLine(name);
}
必要なときだけToList()を使うのが基本です。
4. 配列でSelectを使う基本サンプル
4-1. 配列の各要素を変換する
SelectはList<T>だけでなく、配列にも使えます。
C#int[] numbers = { 1, 2, 3, 4, 5 };
var doubledNumbers = numbers.Select(number => number * 2);
foreach (var number in doubledNumbers)
{
Console.WriteLine(number);
}
実行結果は次のようになります。
C#2
4
6
8
10
文字列配列でも同じように使えます。
C#string[] names = { "tanaka", "sato", "suzuki" };
var upperNames = names.Select(name => name.ToUpper());
foreach (var name in upperNames)
{
Console.WriteLine(name);
}
実行結果は次のとおりです。
C#TANAKA
SATO
SUZUKI
配列でもリストでも、Selectの基本的な書き方は同じです。
4-2. Selectの結果をToArrayで配列に変換する
Selectの結果を配列にしたい場合は、ToArray()を使います。
C#int[] numbers = { 1, 2, 3 };
int[] doubledNumbers = numbers
.Select(number => number * 2)
.ToArray();
文字列配列に変換する例も見てみましょう。
C#int[] numbers = { 1, 2, 3 };
string[] labels = numbers
.Select(number => $"No.{number}")
.ToArray();
この場合、labelsの中身は次のようになります。
C#No.1
No.2
No.3
Selectの結果を配列として使いたい場合はToArray()、リストとして使いたい場合はToList()を使います。
4-3. 配列とListでSelectの使い方はどう違うか
配列とList<T>で、Selectの使い方自体に大きな違いはありません。
C#var list = new List<int> { 1, 2, 3 };
var listResult = list.Select(x => x * 2);
int[] array = { 1, 2, 3 };
var arrayResult = array.Select(x => x * 2);
どちらも同じようにSelectを呼び出せます。
違いが出るのは、変換後の結果を何として受け取りたいかです。
リストとして受け取りたい場合はToList()です。
C#List<int> result = array
.Select(x => x * 2)
.ToList();
配列として受け取りたい場合はToArray()です。
C#int[] result = list
.Select(x => x * 2)
.ToArray();
つまり、Selectの使い方は同じで、最後にToList()を使うかToArray()を使うかが変わります。
5. オブジェクトのリストをSelectで変換する
5-1. クラスの一覧から名前だけを取得する
実務では、クラスの一覧から必要な情報だけを取り出すためにSelectを使うことがよくあります。
次のようなUserクラスがあるとします。
C#public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}
ユーザー一覧を用意します。
C#var users = new List<User>
{
new User { Id = 1, Name = "田中", Age = 25 },
new User { Id = 2, Name = "佐藤", Age = 30 },
new User { Id = 3, Name = "鈴木", Age = 28 }
};
名前だけを取得するには、次のように書きます。
C#var names = users.Select(user => user.Name);
foreach (var name in names)
{
Console.WriteLine(name);
}
実行結果は次のとおりです。
C#田中
佐藤
鈴木
このように、オブジェクトの一覧から特定のプロパティだけを取り出す処理は、Selectの代表的な使い方です。
5-2. 複数のプロパティを使って新しいオブジェクトを作る
Selectでは、複数のプロパティを使って新しいオブジェクトを作ることもできます。
たとえば、Userから表示用のUserSummaryを作る例です。
C#public class UserSummary
{
public string DisplayName { get; set; } = "";
public string Description { get; set; } = "";
}
Selectで変換します。
C#var summaries = users.Select(user => new UserSummary
{
DisplayName = user.Name,
Description = $"{user.Name}さんは{user.Age}歳です"
});
結果を表示してみます。
C#foreach (var summary in summaries)
{
Console.WriteLine(summary.DisplayName);
Console.WriteLine(summary.Description);
}
このように、元のオブジェクトをそのまま使うのではなく、画面表示やAPIレスポンスに必要な形へ変換できます。
5-3. 匿名型に変換する
一時的に使うだけのデータであれば、匿名型に変換することもできます。
C#var result = users.Select(user => new
{
user.Id,
user.Name
});
このコードでは、IdとNameだけを持つ匿名型の一覧を作っています。
プロパティ名を変更することもできます。
C#var result = users.Select(user => new
{
UserId = user.Id,
DisplayName = user.Name
});
匿名型は、メソッド内だけで一時的に使うデータを作るときに便利です。
C#foreach (var item in result)
{
Console.WriteLine($"{item.UserId}: {item.DisplayName}");
}
ただし、メソッドの戻り値として使いたい場合や、複数の場所で使い回す場合は、DTOやViewModelなどの明示的なクラスを作った方がよいです。
5-4. DTOやViewModelに変換する
WebアプリケーションやAPI開発では、エンティティをDTOやViewModelに変換する場面がよくあります。
たとえば、次のようなDTOを用意します。
C#public class UserDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
UserのリストをUserDtoのリストに変換するには、次のように書きます。
C#var userDtos = users
.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name
})
.ToList();
このような変換は、APIのレスポンスを作るときにもよく使われます。
C#public List<UserDto> GetUsers()
{
var users = new List<User>
{
new User { Id = 1, Name = "田中", Age = 25 },
new User { Id = 2, Name = "佐藤", Age = 30 }
};
return users
.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name
})
.ToList();
}
エンティティをそのまま外部に返すのではなく、必要な情報だけをDTOに変換することで、安全で扱いやすいコードになります。
6. Selectの応用的な使い方
6-1. インデックス付きSelectを使う
Selectには、要素だけでなくインデックスも受け取れる overload があります。
C#var names = new List<string> { "田中", "佐藤", "鈴木" };
var result = names.Select((name, index) => $"{index + 1}. {name}");
foreach (var item in result)
{
Console.WriteLine(item);
}
実行結果は次のとおりです。
C#1. 田中
2. 佐藤
3. 鈴木
インデックス付きSelectでは、ラムダ式の引数を2つ書きます。
C#.Select((要素, インデックス) => 変換後の値)
たとえば、一覧に連番を付けたい場合に便利です。
C#var usersWithNumber = users.Select((user, index) => new
{
No = index + 1,
user.Name
});
インデックスは0から始まるため、画面表示用の番号にしたい場合はindex + 1とすることが多いです。
6-2. Whereと組み合わせて条件に合う要素だけ変換する
SelectはWhereと組み合わせて使うことがよくあります。
Whereで条件に合う要素だけを絞り込み、その後にSelectで変換します。
C#var users = new List<User>
{
new User { Id = 1, Name = "田中", Age = 18 },
new User { Id = 2, Name = "佐藤", Age = 25 },
new User { Id = 3, Name = "鈴木", Age = 30 }
};
var adultNames = users
.Where(user => user.Age >= 20)
.Select(user => user.Name);
このコードでは、20歳以上のユーザーだけを抽出し、その名前だけを取得しています。
実行結果は次のようになります。
C#佐藤
鈴木
Whereは絞り込み、Selectは変換です。
C#.Where(user => user.Age >= 20)
.Select(user => user.Name)
この順番で書くことで、「条件に合うものだけを変換する」という意図が明確になります。
6-3. OrderByと組み合わせて並び替えてから変換する
OrderByと組み合わせると、並び替えた後に必要な値だけを取り出せます。
C#var orderedNames = users
.OrderBy(user => user.Age)
.Select(user => user.Name);
このコードでは、年齢の昇順に並び替えてから、名前だけを取得しています。
降順にしたい場合はOrderByDescendingを使います。
C#var orderedNames = users
.OrderByDescending(user => user.Age)
.Select(user => user.Name);
複数条件で並び替えることもできます。
C#var result = users
.OrderBy(user => user.Age)
.ThenBy(user => user.Name)
.Select(user => new
{
user.Name,
user.Age
});
Where、OrderBy、Selectを組み合わせると、データの抽出・並び替え・変換を読みやすく書けます。
6-4. nullを含むデータをSelectで安全に扱う
データの中にnullが含まれる場合、Selectの中でそのままプロパティにアクセスすると例外が発生することがあります。
C#var users = new List<User?>
{
new User { Id = 1, Name = "田中", Age = 25 },
null,
new User { Id = 2, Name = "佐藤", Age = 30 }
};
var names = users.Select(user => user.Name);
このコードでは、userがnullのときにNullReferenceExceptionが発生します。
安全に扱うには、null条件演算子を使います。
C#var names = users.Select(user => user?.Name);
この場合、userがnullなら結果もnullになります。
nullを除外したい場合は、Whereを組み合わせます。
C#var names = users
.Where(user => user != null)
.Select(user => user!.Name);
文字列がnullの場合にデフォルト値を設定することもできます。
C#var names = users.Select(user => user?.Name ?? "名前なし");
Selectでnullを扱うときは、「要素自体がnullの可能性があるのか」「プロパティがnullの可能性があるのか」を意識することが大切です。
6-5. DictionaryやKeyValuePairをSelectで変換する
Dictionary<TKey, TValue>にもSelectを使えます。
C#var scores = new Dictionary<string, int>
{
{ "田中", 80 },
{ "佐藤", 90 },
{ "鈴木", 75 }
};
DictionaryをSelectで処理すると、各要素はKeyValuePair<TKey, TValue>として扱われます。
C#var result = scores.Select(score => $"{score.Key}さんの点数は{score.Value}点です");
foreach (var item in result)
{
Console.WriteLine(item);
}
実行結果は次のとおりです。
C#田中さんの点数は80点です
佐藤さんの点数は90点です
鈴木さんの点数は75点です
匿名型に変換することもできます。
C#var result = scores.Select(score => new
{
Name = score.Key,
Score = score.Value,
IsPassed = score.Value >= 80
});
Dictionaryのキーと値を使って別の形式に変換したい場合にも、Selectは便利です。
7. SelectManyとの違い
7-1. Selectは1要素を1要素に変換する
Selectは、基本的に1つの要素を1つの結果に変換します。
C#var numbers = new List<int> { 1, 2, 3 };
var result = numbers.Select(x => x * 2);
この場合、元の要素数は3件で、変換後の要素数も3件です。
C#1 => 2
2 => 4
3 => 6
オブジェクトのリストから名前だけを取り出す場合も、1件のユーザーから1件の名前を作っています。
C#var names = users.Select(user => user.Name);
つまり、Selectは「1件を1件に変換する」と考えるとわかりやすいです。
7-2. SelectManyは入れ子のコレクションを平坦化する
SelectManyは、入れ子になったコレクションを平坦化するときに使います。
たとえば、次のようなリストがあるとします。
C#var groups = new List<List<string>>
{
new List<string> { "A", "B" },
new List<string> { "C", "D" },
new List<string> { "E" }
};
Selectを使うと、結果はリストのリストのままです。
C#var selected = groups.Select(group => group);
一方、SelectManyを使うと、中の要素を1つのコレクションにまとめられます。
C#var flattened = groups.SelectMany(group => group);
foreach (var item in flattened)
{
Console.WriteLine(item);
}
実行結果は次のとおりです。
C#A
B
C
D
E
SelectManyは「複数のコレクションを1つに広げる」ためのメソッドです。
7-3. Selectを使うべきケース
Selectを使うべきなのは、各要素を1つの値や1つのオブジェクトに変換したい場合です。
C#var names = users.Select(user => user.Name);
ユーザー1件から名前1件を取り出しています。
C#var userDtos = users.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name
});
ユーザー1件からDTO1件を作っています。
このように、元の要素と変換後の要素が対応している場合はSelectを使います。
C#User => UserDto
User => string
int => int
string => string
1件を1件に変換するならSelectです。
7-4. SelectManyを使うべきケース
SelectManyを使うべきなのは、各要素がさらにコレクションを持っていて、それらをまとめて1つの一覧にしたい場合です。
たとえば、ユーザーごとに複数の注文を持っているケースです。
C#public class User
{
public string Name { get; set; } = "";
public List<Order> Orders { get; set; } = new();
}
public class Order
{
public int Id { get; set; }
public int Amount { get; set; }
}
全ユーザーの注文を1つの一覧にしたい場合は、SelectManyを使います。
C#var allOrders = users.SelectMany(user => user.Orders);
Selectを使うと、ユーザーごとの注文リストがそのまま返されます。
C#var orderLists = users.Select(user => user.Orders);
この場合、結果はIEnumerable<List<Order>>のような形になります。
一方、SelectManyを使うと、結果はIEnumerable<Order>になります。
C#var orders = users.SelectMany(user => user.Orders);
入れ子のリストを平坦化したいなら、SelectManyを使います。
7-5. SelectとSelectManyの違いがわかるサンプルコード
SelectとSelectManyの違いを、具体的なコードで確認してみましょう。
C#var groups = new List<List<string>>
{
new List<string> { "A", "B" },
new List<string> { "C", "D" }
};
まず、Selectを使います。
C#var selected = groups.Select(group => group);
foreach (var group in selected)
{
Console.WriteLine(string.Join(",", group));
}
実行結果は次のとおりです。
C#A,B
C,D
Selectでは、各グループがそのまま返されています。
次に、SelectManyを使います。
C#var flattened = groups.SelectMany(group => group);
foreach (var item in flattened)
{
Console.WriteLine(item);
}
実行結果は次のとおりです。
C#A
B
C
D
SelectManyでは、入れ子のリストが平坦化され、すべての文字列が1つの一覧として取り出されています。
違いをまとめると、次のようになります。
C#Select : List<List<string>> の形を保つ
SelectMany : List<string> のように平坦化する
迷ったときは、「変換後に入れ子のコレクションが残ってほしいか」を考えると判断しやすいです。
8. Selectでよくあるエラーと注意点
8-1. Selectしただけでは処理が実行されない理由
Selectは遅延実行されます。
つまり、Selectを書いただけでは、その場ですぐに処理が実行されるとは限りません。
C#var numbers = new List<int> { 1, 2, 3 };
var result = numbers.Select(x =>
{
Console.WriteLine($"変換: {x}");
return x * 2;
});
この時点では、まだConsole.WriteLineは実行されません。
実際に実行されるのは、foreachで列挙したときです。
C#foreach (var number in result)
{
Console.WriteLine(number);
}
または、ToList()やToArray()を呼び出したときにも実行されます。
C#var list = result.ToList();
LINQの遅延実行を理解していないと、「Selectを書いたのに処理されない」と感じることがあります。
8-2. ToListやToArrayが必要になるケース
Selectの結果をすぐに確定させたい場合は、ToList()やToArray()を使います。
C#var result = numbers
.Select(x => x * 2)
.ToList();
たとえば、次のように元のリストを後から変更する場合、遅延実行の影響を受けることがあります。
C#var numbers = new List<int> { 1, 2, 3 };
var result = numbers.Select(x => x * 2);
numbers.Add(4);
foreach (var number in result)
{
Console.WriteLine(number);
}
実行結果は次のようになります。
C#2
4
6
8
Selectの時点ではまだ実行されていないため、後から追加した4も対象になります。
その時点の結果を固定したい場合は、ToList()を使います。
C#var result = numbers
.Select(x => x * 2)
.ToList();
numbers.Add(4);
foreach (var number in result)
{
Console.WriteLine(number);
}
この場合、resultには最初の1, 2, 3を変換した結果だけが入ります。
8-3. 型推論でエラーになる原因
Selectでは、ラムダ式の戻り値の型から結果の型が推論されます。
たとえば、次のコードはIEnumerable<string>になります。
C#var result = numbers.Select(x => x.ToString());
一方、条件によって異なる型を返そうとするとエラーになることがあります。
C#var result = numbers.Select(x =>
{
if (x % 2 == 0)
{
return x;
}
return x.ToString();
});
このコードでは、偶数の場合はint、奇数の場合はstringを返そうとしているため、型が一致しません。
戻り値の型はそろえる必要があります。
C#var result = numbers.Select(x =>
{
if (x % 2 == 0)
{
return x.ToString();
}
return $"odd:{x}";
});
また、匿名型を返す場合も、条件分岐ごとにプロパティ構成をそろえる必要があります。
C#var result = users.Select(user => new
{
user.Id,
user.Name
});
Selectでは「各要素を何型に変換するのか」を明確にすると、型推論のエラーを防ぎやすくなります。
8-4. null参照例外を防ぐ書き方
Selectでオブジェクトのプロパティを取り出す場合、null参照例外に注意が必要です。
C#var names = users.Select(user => user.Name);
このコードは、usersの中にnullが含まれていると例外になります。
安全に書くなら、null条件演算子を使います。
C#var names = users.Select(user => user?.Name);
nullを除外したい場合は、Whereで絞り込みます。
C#var names = users
.Where(user => user != null)
.Select(user => user!.Name);
プロパティがnullの可能性がある場合は、null合体演算子を使うと安全です。
C#var names = users.Select(user => user.Name ?? "名前なし");
ネストしたプロパティにアクセスする場合も注意が必要です。
C#var companyNames = users.Select(user => user.Company.Name);
Companyがnullの可能性があるなら、次のように書きます。
C#var companyNames = users.Select(user => user.Company?.Name ?? "会社名なし");
Selectの中では、nullの可能性がある値にそのままアクセスしないことが重要です。
8-5. foreachとの違いを理解する
Selectとforeachは似ているように見えますが、目的が異なります。
Selectは変換結果を作るために使います。
C#var doubledNumbers = numbers.Select(x => x * 2);
一方、foreachは各要素に対して処理を実行するために使います。
C#foreach (var number in numbers)
{
Console.WriteLine(number);
}
変換した結果が欲しい場合はSelectが向いています。
C#var names = users.Select(user => user.Name).ToList();
ログ出力、ファイル書き込み、外部サービス呼び出しなど、副作用が目的の場合はforeachの方が自然です。
C#foreach (var user in users)
{
Console.WriteLine(user.Name);
}
Selectの中で副作用のある処理を行うこともできますが、読みづらくなるため避けた方がよいです。
C#users.Select(user =>
{
Console.WriteLine(user.Name);
return user.Name;
});
このような処理はforeachで書いた方が意図が明確です。
9. Selectを使うメリット
9-1. コードを短く読みやすく書ける
Selectを使うと、変換処理を短く書けます。
たとえば、foreachで名前一覧を作る場合は次のようになります。
C#var names = new List<string>();
foreach (var user in users)
{
names.Add(user.Name);
}
Selectを使うと、同じ処理を次のように書けます。
C#var names = users
.Select(user => user.Name)
.ToList();
コードの行数が減り、「ユーザー一覧から名前一覧を作る」という意図もわかりやすくなります。
単純な変換処理であれば、foreachよりもSelectの方がすっきり書けることが多いです。
9-2. 変換処理の意図が明確になる
Selectを使うと、「この処理は変換である」という意図が明確になります。
C#var userDtos = users.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name
});
このコードを見ると、UserからUserDtoへ変換していることがすぐにわかります。
WhereやOrderByと組み合わせた場合も、処理の流れが読みやすくなります。
C#var result = users
.Where(user => user.Age >= 20)
.OrderBy(user => user.Name)
.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name
})
.ToList();
このコードは、次の流れを表しています。
C#20歳以上に絞り込む
名前順に並び替える
DTOに変換する
リストにする
LINQを使うことで、データ処理の流れを自然に表現できます。
9-3. foreachよりもLINQが向いているケース
foreachよりもSelectが向いているのは、変換結果を作ることが主目的のケースです。
C#var productNames = products.Select(product => product.Name).ToList();
また、WhereやOrderByなどと組み合わせる処理にも向いています。
C#var result = products
.Where(product => product.Price >= 1000)
.OrderBy(product => product.Price)
.Select(product => product.Name)
.ToList();
このように、絞り込み・並び替え・変換を一連の流れとして書けるのがLINQの強みです。
一方で、複雑な条件分岐や副作用の多い処理では、foreachの方が読みやすいこともあります。
C#foreach (var product in products)
{
if (product.Price >= 1000)
{
Console.WriteLine(product.Name);
}
}
Selectを使うかforeachを使うかは、処理の目的で判断するとよいです。
9-4. 可読性を下げないための書き方
Selectは便利ですが、何でも1行で書けばよいわけではありません。
次のように処理を詰め込みすぎると、読みにくくなります。
C#var result = users.Select(user => new UserDto { Id = user.Id, Name = user.Name.Trim().ToUpper(), IsAdult = user.Age >= 20, Label = $"{user.Id}:{user.Name}" }).ToList();
このような場合は、改行して書くと読みやすくなります。
C#var result = users
.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name.Trim().ToUpper(),
IsAdult = user.Age >= 20,
Label = $"{user.Id}:{user.Name}"
})
.ToList();
さらに複雑な変換であれば、メソッドに切り出すのも有効です。
C#var result = users
.Select(user => ToUserDto(user))
.ToList();
UserDto ToUserDto(User user)
{
return new UserDto
{
Id = user.Id,
Name = user.Name,
IsAdult = user.Age >= 20
};
}
Selectは読みやすく書ける一方で、使い方によっては逆に読みにくくなります。複雑になりすぎる場合は、適度に改行したり、メソッド化したりすることが大切です。
10. C#のSelectに関するよくある質問
10-1. SelectとWhereの違いは?
Selectは要素を変換するメソッドです。
C#var names = users.Select(user => user.Name);
このコードは、ユーザー一覧を名前一覧に変換しています。
一方、Whereは条件に合う要素だけを絞り込むメソッドです。
C#var adults = users.Where(user => user.Age >= 20);
このコードは、20歳以上のユーザーだけを抽出しています。
違いを簡単にまとめると、Selectは変換、Whereは絞り込みです。
C#Select : User => string
Where : Userの中から条件に合うUserだけ残す
両方を組み合わせることもよくあります。
C#var adultNames = users
.Where(user => user.Age >= 20)
.Select(user => user.Name);
このコードでは、20歳以上のユーザーに絞り込んでから、名前だけを取り出しています。
10-2. Selectとforeachはどちらを使うべき?
変換結果を作りたい場合はSelectが向いています。
C#var names = users.Select(user => user.Name).ToList();
各要素に対して処理を実行したい場合はforeachが向いています。
C#foreach (var user in users)
{
Console.WriteLine(user.Name);
}
たとえば、リストから別のリストを作るならSelectが読みやすいです。
C#var userDtos = users
.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name
})
.ToList();
ログ出力や状態変更が主目的ならforeachが自然です。
C#foreach (var user in users)
{
user.LastAccessedAt = DateTime.Now;
}
Selectは「変換」、foreachは「処理の実行」と考えると使い分けやすいです。
10-3. Selectの結果をListにするには?
Selectの結果をList<T>にするには、最後にToList()を付けます。
C#var numbers = new List<int> { 1, 2, 3 };
List<int> result = numbers
.Select(x => x * 2)
.ToList();
オブジェクトのリストをDTOのリストに変換する場合も同じです。
C#List<UserDto> userDtos = users
.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name
})
.ToList();
ToList()を付けない場合、戻り値はIEnumerable<T>になります。
C#IEnumerable<UserDto> userDtos = users.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name
});
List<T>として使いたい場合は、ToList()を忘れないようにしましょう。
10-4. Selectで複数の値を返すには?
Selectで複数の値を返したい場合は、匿名型やクラスを使います。
匿名型を使う例です。
C#var result = users.Select(user => new
{
user.Id,
user.Name,
user.Age
});
プロパティ名を変更することもできます。
C#var result = users.Select(user => new
{
UserId = user.Id,
DisplayName = user.Name,
IsAdult = user.Age >= 20
});
複数の場所で使うデータであれば、DTOクラスを作るのがおすすめです。
C#public class UserDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
public bool IsAdult { get; set; }
}
SelectでDTOに変換します。
C#var result = users.Select(user => new UserDto
{
Id = user.Id,
Name = user.Name,
IsAdult = user.Age >= 20
});
一時的な処理なら匿名型、外部に返すデータや再利用するデータならDTOを使うとよいです。
10-5. SelectManyはいつ必要になる?
SelectManyは、入れ子になったコレクションを1つにまとめたいときに必要です。
たとえば、ユーザーごとに注文リストを持っているとします。
C#var users = new List<User>
{
new User
{
Name = "田中",
Orders = new List<Order>
{
new Order { Id = 1, Amount = 1000 },
new Order { Id = 2, Amount = 2000 }
}
},
new User
{
Name = "佐藤",
Orders = new List<Order>
{
new Order { Id = 3, Amount = 3000 }
}
}
};
全ユーザーの注文を1つの一覧にしたい場合は、SelectManyを使います。
C#var allOrders = users.SelectMany(user => user.Orders);
Selectを使うと、注文リストの一覧になります。
C#var orderLists = users.Select(user => user.Orders);
Selectは1要素を1要素に変換するメソッドです。SelectManyは、1要素から複数要素を取り出し、それらをまとめて平坦化するメソッドです。
入れ子のコレクションをフラットにしたいときは、SelectManyを使いましょう。
まとめ
C#のSelectは、LINQでコレクションの各要素を別の形に変換するためのメソッドです。
数値の加工、文字列の整形、オブジェクトから特定プロパティの抽出、DTOやViewModelへの変換など、実務でも非常によく使われます。
基本形は次のとおりです。
C#var result = collection.Select(item => 変換後の値);
Selectの戻り値は基本的にIEnumerable<T>なので、リストとして使いたい場合はToList()、配列として使いたい場合はToArray()を使います。
C#var list = collection.Select(item => item.Name).ToList();
var array = collection.Select(item => item.Name).ToArray();
Whereは絞り込み、Selectは変換です。
C#var result = users
.Where(user => user.Age >= 20)
.Select(user => user.Name)
.ToList();
また、SelectとSelectManyの違いも重要です。
Selectは1要素を1要素に変換します。
C#var names = users.Select(user => user.Name);
SelectManyは、入れ子のコレクションを平坦化します。
C#var allOrders = users.SelectMany(user => user.Orders);
C#でリストや配列を扱うなら、Selectは必ず理解しておきたいLINQメソッドです。単純な変換からオブジェクト変換まで幅広く使えるため、基本構文とToList()、ToArray()、WhereやSelectManyとの違いを押さえておくと、より読みやすく保守しやすいコードを書けるようになります。

