概要
2025年11月に.NET 10とC# 14がリリースされました。
この投稿では、.NET 10でLINQ to Objectsに追加された
- Sequence
- InfiniteSequence
- Shuffle
- LeftJoin
- RightJoin
について紹介します。
また、Reverseのオーバーロード追加についても紹介します。
過去の追加されたLINQメソッドの記事はこちら
Sequence
Sequenceメソッドは、start、endInclusive、stepを引数にとり、シーケンス(IEnumerable<T>)を生成するメソッドです。
startから始まって、endInclusiveに到達するまで、stepごとに増分した値をとるシーケンスを生成します。
[Fact]
public void SequenceTest()
{
var sequence = Enumerable.Sequence(start: 1, endInclusive: 10, step: 2);
Assert.Equal([1, 3, 5, 7, 9], sequence);
}
Sequenceメソッドの型パラメーターである「T」の制約は、「System.Numerics.INumber<T>」です。
end"Incusive"(包括的)なので、endIncluiveと同じ値はシーケンスの最後の要素とし含まれます。
[Fact]
public void SequenceTestWithEndExclusive()
{
var sequence = Enumerable.Sequence(start: 1, endInclusive: 9, step: 2);
Assert.Equal([1, 3, 5, 7, 9], sequence);
}
startとendIncluiveが同じで、かつstepが0な場合、stepの値を1個だけ持つシーケンスを生成します。
[Fact]
public void SequenceWithSingleValue()
{
var sequence = Enumerable.Sequence(start: 1, endInclusive: 1, step: 0);
Assert.Equal([1], sequence);
}
終了しない条件を引数に渡すと、即座にArgumentOutOfRangeExceptionが投げられます。
[Fact]
public void SequenceTestWithArgumentOutOfRangeException()
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Enumerable.Sequence(start: 1, endInclusive: 9, step: -1);
});
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Enumerable.Sequence(start: 1, endInclusive: -5, step: 2);
});
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Enumerable.Sequence(start: 1, endInclusive: 10, step: 0);
});
}
似ているメソッドに「Enumerable.Range」があります。Rangeは、初期値startと要素数countを引数にとります。
[Fact]
public void RangeTest()
{
var sequence = Enumerable.Range(start: 1, count: 4);
Assert.Equal([1, 2, 3, 4], sequence);
}
Enumerable.SequenceとEnumerable.Rangeは、名前付き引数で書かれていないコードを読む際は、都度リファレンスで挙動を確認したいです。(覚えられなさそう。)
[Fact]
public void SequenceRangeNoNamedArgument()
{
// 第二引数は要素数だっけ?終了条件だっけ?
var sequence = Enumerable.Sequence(3, 5, 1);
Assert.Equal([3, 4, 5], sequence);
// 第二引数は要素数だっけ?終了条件だっけ?
var range = Enumerable.Range(3, 5);
Assert.Equal([3, 4, 5, 6, 7], range);
}
InfiniteSequence
InfiniteSequenceメソッドは、startとstepを引数にとり、終了しないシーケンス(IEnumerable<T>)を生成するメソッドです。startから始まって、stepごとに増分した値をとるシーケンスを生成します。
[Fact]
public void InfiniteSequence()
{
var infiniteSequence = Enumerable.InfiniteSequence(start: 0, step: 1);
Assert.Equal([0, 1, 2, 3, 4], infiniteSequence.Take(5));
}
実用上はTakeなどと組み合わせることが多そうです。
InfiniteSequenceメソッドの型パラメーターである「T」の制約は、「System.Numerics.IAdditionOperators<T,T,T>」です。
Shuffle
Shuffleメソッドは、要素をランダムに並び替えたシーケンス(IEnumerable<T>)を生成するメソッドです。
呼び出し元のIEnumerable<T>はそのままで、新たに並び替えたIEnumerable<T>を返します。
[Fact]
public void ShuffleTest()
{
var shuffled = new[] { 3, 1, 4, 1, 5, 9, 2 }.Shuffle();
Assert.Equal(7, shuffled.Count());
Assert.Equal([1, 1, 2, 3, 4, 5, 9], shuffled.Order());
}
配列やSpan<T>の中身を入れ替えたい場合(インプレースシャッフルしたい場合)、RandomクラスのShuffleメソッドを使いましょう。
var array = Enumerable.Range(1, 10).ToArray();
Random.Shared.Shuffle(array);
LeftJoinとRightJoin
LeftJoinメソッドは左外部結合を、RightJoinは右外部結合を行うメソッドです。
どちらも2つのシーケンスを一致するキーに基づいて要素を結合・変換し、新しいシーケンスを作成するメソッドです。
LINQでは、初期からJoinメソッドにより内部結合を行うことができました。また、GroupJoinというメソッドもありました。しかしLINQには、.NET 10まで左外部結合を行うメソッドはありませんでした。今までは、公式サイトでも左外部結合をやりたいのであれば、「GroupJoinメソッドとDefaultIfEmptyメソッドを組み合わる」と紹介されていました。
.NET 10からLeftJoinメソッドが登場し、単一のメソッドで実現できます。
次のレコードを使って例を示します。
private record Student(
string Id,
string Name
);
private record TestScore(
string StudentId,
string Subject,
int Score
);
private record TestResult(
string? StudentName,
string? Subject,
int? Score
);
LeftJoinを使った左外部結合の例を次に示します。
[Fact]
public void LeftJoinTest()
{
var students = new Student[]
{
new("1", "Taro"),
new("2", "Jiro"),
new("3", "Saburo")
};
var testScores = new TestScore[]
{
new("1", "Math", 90),
new("1", "English", 100),
new("2", "Math", 60),
new("4", "Math", 80)
};
var results = students
.LeftJoin(testScores,
student => student.Id,
score => score.StudentId,
(student, score) => new TestResult(student.Name, score?.Subject, score?.Score)
);
Assert.Equal([
new TestResult("Taro", "Math", 90),
new TestResult("Taro", "English", 100),
new TestResult("Jiro", "Math", 60),
new TestResult("Saburo", null, null)
], results);
}
RightJoinを使った右外部結合の例を次に示します。
[Fact]
public void RightJoinTest()
{
var students = new Student[]
{
new("1", "Taro"),
new("2", "Jiro"),
new("3", "Saburo")
};
var testScores = new TestScore[]
{
new("1", "Math", 90),
new("1", "English", 100),
new("2", "Math", 60),
new("4", "Math", 80)
};
var results = students
.RightJoin(testScores,
student => student.Id,
score => score.StudentId,
(student, score) => new TestResult(student?.Name, score.Subject, score.Score)
);
Assert.Equal([
new TestResult("Taro", "Math", 90),
new TestResult("Taro", "English", 100),
new TestResult("Jiro", "Math", 60),
new TestResult(null, "Math", 80)
], results);
}
Reverse
Reverseメソッドに配列専用のオーバーロードが加わりました。これは「新しく何かできるようにするため」ではなく、「互換性を守るため」の追加です。
C#には、IEnumerable<T>型の拡張メソッドとして、Reverseというメソッドがもともとありました。
また、Span<T>型の拡張メソッドとしても、Reverseというメソッドもありました。
次のようなReverseメソッドを呼び出しているコードを例に説明します。
var arrary = new[] { 3, 1, 4, 1, 5, 9, 2 };
arrary.Reverse();
C# 13以前では、上記のコードはIEnumerable<T>型の拡張メソッドReverseが呼び出されていました。
ところで、C# 14で「First-class Span Types」が導入されました。
「.NET 10でReverseメソッドに配列専用のオーバーロードが追加されなかった」と仮定します。その場合、上記のコードはC# 14ではSpan<T>型の拡張メソッドReverseが呼び出されることになります。つまり、C# 13以前とC# 14以降で動作が変わってしまうのです。
そのような動作・挙動の変更を防ぎ、互換性を守るために、Reverseメソッドに配列専用のオーバーロードが.NET 10で追加されました。
まとめ
この投稿では、.NET 10で追加されたLINQメソッドを紹介しました。.NET 10プロジェクトで、どんどん活用していきましょう!