まえがき
最近までGoを書いていた私がC#に触れ始めました。
今回は コレクション操作(List / slice / map)について理解したことを、Goとの比較を踏まえながら記事にしていこうと思います!
また比較といっていますが両言語は思想が異なるため、優劣をつける意図はありません。用途と前提が違うだけという視点でフラットに見ていきます。
簡単に結論
C#
- 宣言的表現で簡潔に書ける
- LINQなど標準機能が充実
Go
- シンプルな言語仕様
- 基本はforループで明示的に書く
基本操作の比較(ざっくり)
| 操作 | C# | Go |
|---|---|---|
| 追加 | Add() |
append() |
| ループ | foreach |
for range |
| フィルタ | Where() |
forループで自前実装 |
| 変換 | Select() |
forループで自前実装 |
| 検索 | First()/Any() |
forループで自前実装 |
| マップのKey存在チェック | TryGetValue |
value, ok := map[k] |
コレクションの基本
C#
// リスト
var list = new List<int> { 1, 2, 3 };
// Dictionary(≒マップ)
var dict = new Dictionary<string, int>
{
["a"] = 1,
["b"] = 2
};
Go
// スライス(≒リスト)
list := []int{1, 2, 3}
// マップ
dict := map[string]int{
"a": 1,
"b": 2,
}
要素の追加
C#
// リストに追加
var list = new List<int>();
list.Add(1);
// Dictionary(マップ)に追加
var dict = new Dictionary<string, int>();
// マップに追加する処理は以下の2パターンあります。
// 1. Add関数。すでに同じキーが存在する場合は例外が発生します。
dict.Add("a", 1);
// 2. インデクサー。すでに同じキーが存在する場合は上書きします。
dict["a"] = 1;
Go
// スライスに追加
list := []int{}
list = append(list, 1)
// マップに追加
dict := map[string]int{}
dict["a"] = 1
ループ処理
C#
// リストのループ
foreach (var item in list)
{
Console.WriteLine(item);
}
// Dictionary(マップ)のループ
// `var item`は暗黙的に KeyValuePair 型として扱える
foreach (var item in dict)
{
Console.WriteLine($"{item.Key}:{item.Value}");
}
Go
// スライスのループ
for _, item := range list {
fmt.Println(item)
}
// マップのループ
for key, item := range dict {
fmt.Printf("%s:%d", key, item)
}
C# の LINQ
C#には、コレクションの操作を宣言的に簡単に行うことができる「LINQ」という機能があります。
Goには、標準ではそれらの機能は充実しておらず forループ などを使って自前で実装する必要があることが多いです。これは実装の透明性やカスタマイズ性を重視していると感じます。
下記では、LINQ の関数の中からいくつか抜粋して比較をしてみようと思います。
Where
条件に基づいてコレクションをフィルタします。
C#
var result = list.Where(x => x % 2 == 0).ToList();
Go
var result []int
for _, x := range list {
if x%2 == 0 {
result = append(result, x)
}
}
Select
各要素を新しいコレクションに変換します。
C#
var result = list.Select(x => x * 2).ToList();
Go
var result []int
for _, x := range list {
result = append(result, x*2)
}
First
コレクションから最初の要素を取得します。ただし、要素が見つからなければ例外が発生します。
C#
var first = list.First(x => x > 1);
Go
var firstValue int
for _, x := range list {
if x > 1 {
firstValue = x
break
}
}
// 要素が見つからなければ処理が終了
panic("error ocuured")
FirstOrDefault
コレクションから最初の要素を取得します。ただし、要素が見つからなければ default(参照型は null、値型は型既定のデフォルト値)を返します。
C#
var firstOrDefault = list.FirstOrDefault(x => x > 1);
Go
var firstOrDefaultValue int
for _, x := range list {
if x > 1 {
firstOrDefaultValue = x
break
}
}
// 要素が見つからなくても処理は終了せずに、int型のゼロ値(= 0)のまま処理を続行
Any
コレクション内のいずれかの要素が存在するか、または条件を満たすかどうかを判定します。
C#
var exists = list.Any(x => x > 1);
Go
exists := false
for _, x := range list {
if x > 1 {
exists = true
break
}
}
Count
コレクションの要素数を返します。
C#
var count = list
.Count(x => x % 2 == 0);
Go
count := 0
for _, x := range list {
if x%2 == 0 {
count++
}
}
LINQのメソッドチェーン
C#の LINQ を利用するときに最もよく使われるのが、メソッドチェーンになります。
メソッドチェーンを使用することでコレクション操作のコードの見通しがよくなり可読性が上がります。
Go でメソッドチェーンを実現するには、カスタムの型を定義してその型に必要なメソッドを追加・実装していく必要があるため、LINQのメソッドチェーンは非常に強力な機能です。
var result = list
.Where(x => x > 1)
.Select(x => x * 2)
.OrderBy(x => x)
.First();
マップのキーの存在チェック
Go
// 取得と同時にキーの存在判定できる(シンプル)
value, ok := dict["a"]
if ok {
fmt.Println(value)
}
C#
var dict = new Dictionary<string, int>();
// 取得と同時にキーの存在判定できる
if (dict.TryGetValue("a", out var value))
{
Console.WriteLine(value);
}
Go の slices/maps パッケージ(軽く補足)
Goにも補助的ではありますが、コレクション操作に便利な標準パッケージ(slices, maps など)が少しずつ追加されています。
現状では、あくまで補助的であり、中心はforループによる操作が基本になります。
これらのパッケージの中で実際に私が使用していたメソッドをいくつか紹介して比較してみます。
slicesパッケージのContains
コレクションに要素が含まれているかどうかを判定する。
Go
has := slices.Contains(list, 3)
C#
var has = list.Contains(3);
// または
var has2 = list.Any(x => x == 3);
mapsパッケージのKeys
マップのキー一覧を取得する。ただしGoバージョンによっては挙動が異なる可能性があります。
Go
// Goバージョン1.23.0以降では、slices.Collect関数と組み合わせて取得する
keys := slices.Collect(maps.Keys(dict))
C#
Dictionary<string, int>.KeyCollection keys = dict.Keys;
まとめ
- C#は LINQなどの標準機能で宣言的にコレクション操作できる機能が充実している
- Goは forループで明示的に処理を書くことで実装がブラックボックス化しにくい
- Go には、slices / maps パッケージはあるが、現状ではあくまで補助的な立ち位置
個人的な感想
C#のLINQは、Goエンジニアから見ると非常に強力で、「ここまで簡潔に書けるのか」という驚きがありました。
一方でGoの 明示的に書くことを重視した言語仕様も実装の透明化という意味では強力ですが、実装者によってパフォーマンスにばらつきが出やすい所になります。
両言語の思想の違いがよく現れており個人的には調べていて非常に興味深かったかなと思います!