なぜ Span / Memory か
Span<T> と Memory<T> は、配列や文字列の一部をコピーせずに扱うための型です。高スループットが求められる Web API やバッチ処理で、GC 圧を下げる決定打になります。
パターン 1: 文字列分割をコピーしない
string.Split は部分文字列の配列を返しますが、ログパースなど大量に走る処理では毎回 allocation が発生します。ReadOnlySpan<char>.Split(.NET 8 以降、.NET 10 / C# 14 の暗黙スパン変換でさらに書きやすくなった)を使うと無割当で処理できます。
ReadOnlySpan<char> line = "2024-01-15 12:34:56 INFO Started";
foreach (var range in line.Split(' '))
{
var token = line[range];
// token を使った処理(allocation なし)
}
パターン 2: UTF-8 を直接パースする
HTTP リクエストから受け取ったバイトは ReadOnlySpan<byte> です。Encoding.UTF8.GetString で文字列化する前に直接パースできれば、もう 1 段階アロケーションを削れます。
public static bool TryParseInt(ReadOnlySpan<byte> utf8, out int value) =>
System.Buffers.Text.Utf8Parser.TryParse(utf8, out value, out _);
パターン 3: 非同期と Memory の使い分け
Span<T> は ref struct のため async メソッドをまたげません。非同期処理内で持ち回るには Memory<T> を使います。
public async Task ProcessAsync(ReadOnlyMemory<byte> payload, CancellationToken ct)
{
await _pipe.WriteAsync(payload, ct);
// 同期的にスパン化して使う
ParseHeader(payload.Span);
}
やりがちな落とし穴
- クラスのフィールドに
Spanを持てない —Memoryで持ち、メソッド内でSpan化する。 ArrayPoolから借りたバッファは必ず返す —try/finallyでReturnを忘れない。- 範囲外アクセスは
IndexOutOfRangeException— 最初の単体テストで必ず境界をつく。
計測 → 適用の順で
Span/Memory は強力ですが、可読性は下がります。プロファイラで allocation が実際にボトルネックになっていることを確認してから投入するのが鉄則です。
ネイティブ境界を越えるデータ受け渡し(FFI)でも Span/Memory は活躍します。詳しくは C# と Rust の相互運用(FFI)完全ガイド をご覧ください。
このテーマの全体像は .NET パフォーマンス最適化 完全ガイド にまとめています。
この記事をシェア
関連記事

C# に Union 型がやってきた — Result 型で安全・簡潔にエラーを扱う
F# や TypeScript を触ったことがある開発者なら、一度は「C# にも 判別共用体(union 型) があれば」と思ったはずです。…

C# / .NET でトレーディングボットを開発する — 自動取引システムを支える技術的優位性
自動取引システム、いわゆるトレーディングボットは、市場データの受信から売買判断、注文執行、リスク管理までを人手を介さずに回す仕組みです。ミリ秒単位の遅延が結果を左右する一方で、…

C# と Rust の相互運用(FFI)完全ガイド|.NET 10 から P/Invoke で Rust を呼ぶ
C#(.NET)は生産性とエコシステムに優れた言語ですが、計算量の多い数値処理やバイナリ解析、既存の高品質なライブラリがすべて Rust に揃っている領域では、…
