C# AsSpanとは?使い方・メリット・Substringとの違いを初心者向けに解説

はじめに

C#で文字列処理や配列処理を書いていると、AsSpanというメソッドを見かけることがあります。

AsSpanは、文字列や配列の一部を効率よく扱いたいときに使われるメソッドです。特に、Substringのように新しい文字列を作らず、元のデータを参照したまま処理できる点が大きな特徴です。

たとえば、ログ解析、CSVの読み取り、数値文字列のパース、大量のテキスト処理などでは、不要なメモリ割り当てを減らせるため、パフォーマンス改善につながることがあります。

一方で、AsSpanは通常のstringとは扱い方が少し異なるため、初心者にとっては「結局何が便利なのか」「Substringと何が違うのか」が分かりにくい部分もあります。

この記事では、C#のAsSpanについて、基本的な意味、使い方、メリット、Substringとの違い、実践的なサンプルコードまで初心者向けに解説します。

1. C#のAsSpanとは?まず押さえるべき基本

1-1. AsSpanは文字列や配列をSpanとして扱うためのメソッド

AsSpanは、文字列や配列などのデータをSpan<T>またはReadOnlySpan<T>として扱うためのメソッドです。

たとえば、文字列に対してAsSpanを使うと、ReadOnlySpan<char>として扱えます。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan();

このコードでは、textの内容をコピーしているわけではありません。元の文字列textを参照する形で、ReadOnlySpan<char>として扱っています。

つまり、AsSpanは「元データをコピーせずに、必要な範囲を軽量に参照するための仕組み」と考えると分かりやすいです。

1-2. Span<T>・ReadOnlySpan<T>との関係

AsSpanを理解するには、Span<T>ReadOnlySpan<T>の関係を押さえる必要があります。

Span<T>は、配列やメモリ上の連続したデータを表す型です。データの一部を参照し、必要に応じて変更することもできます。

一方、ReadOnlySpan<T>は読み取り専用のSpanです。データを参照できますが、内容を変更することはできません。

文字列はC#では変更できない不変のデータなので、string.AsSpan()の戻り値はReadOnlySpan<char>になります。

C#
string text = "CSharp";

ReadOnlySpan<char> span = text.AsSpan();

配列の場合は、変更可能なSpan<T>として扱うことができます。

C#
int[] numbers = { 1, 2, 3, 4, 5 };

Span<int> span = numbers.AsSpan();
span[0] = 100;

Console.WriteLine(numbers[0]); // 100

このように、AsSpanは対象が文字列なのか配列なのかによって、扱えるSpanの種類が変わります。

1-3. AsSpanが使われる主な場面

AsSpanは、主に次のような場面で使われます。

文字列の一部を切り出して比較したいとき、配列の一部だけを処理したいとき、大量のテキストを高速に解析したいとき、不要なメモリ割り当てを減らしたいときなどです。

たとえば、次のように文字列の一部を取り出して比較できます。

C#
string text = "ERROR: File not found";

ReadOnlySpan<char> level = text.AsSpan(0, 5);

if (level.SequenceEqual("ERROR"))
{
Console.WriteLine("エラーログです");
}

このコードでは、"ERROR"の部分を取り出していますが、Substringのように新しい文字列を生成していません。

そのため、大量のログを処理するような場面では、AsSpanを使うことでメモリ効率のよい処理が書けます。

1-4. 初心者がAsSpanで混乱しやすいポイント

初心者がAsSpanで混乱しやすいポイントは、AsSpanが「文字列を切り出すメソッド」に見えることです。

たしかに、使い方だけ見るとSubstringに似ています。

C#
string text = "Hello World";

var span = text.AsSpan(0, 5);

しかし、AsSpanは新しい文字列を作るわけではありません。元の文字列の一部を参照するだけです。

そのため、戻り値もstringではなくReadOnlySpan<char>です。

C#
string text = "Hello World";

string sub = text.Substring(0, 5); // string
ReadOnlySpan<char> span = text.AsSpan(0, 5); // ReadOnlySpan<char>

この違いを理解することが、AsSpanを使いこなす第一歩です。

2. AsSpanを理解するための前提知識

2-1. Span<T>とは何か

Span<T>は、配列やメモリ上の連続したデータを表すための型です。

たとえば、int[]の一部だけをSpan<int>として扱うことができます。

C#
int[] numbers = { 10, 20, 30, 40, 50 };

Span<int> span = numbers.AsSpan(1, 3);

foreach (int number in span)
{
Console.WriteLine(number);
}

この場合、出力されるのは次の3つです。

C#
20
30
40

Span<T>は、元の配列の一部を参照しているだけなので、データをコピーしません。

さらに、Span<T>を通じて値を変更すると、元の配列にも反映されます。

C#
int[] numbers = { 10, 20, 30, 40, 50 };

Span<int> span = numbers.AsSpan(1, 3);
span[0] = 999;

Console.WriteLine(numbers[1]); // 999

このように、Span<T>は配列の一部を効率よく操作するために便利です。

2-2. ReadOnlySpan<T>とは何か

ReadOnlySpan<T>は、読み取り専用のSpanです。

Span<T>と同じようにメモリ上の連続したデータを参照できますが、内容を変更することはできません。

文字列に対してAsSpanを使うと、ReadOnlySpan<char>になります。

C#
string text = "Hello";

ReadOnlySpan<char> span = text.AsSpan();

Console.WriteLine(span[0]); // H

次のように文字を変更しようとすると、コンパイルエラーになります。

C#
string text = "Hello";

ReadOnlySpan<char> span = text.AsSpan();

// span[0] = 'h'; // エラー

これは、stringが不変の型だからです。

ReadOnlySpan<T>は、「変更しない前提で、元データを効率よく参照するための型」と考えると分かりやすいです。

2-3. メモリのコピーと参照の違い

AsSpanを理解するうえで重要なのが、コピーと参照の違いです。

コピーとは、元のデータとは別に新しいデータを作ることです。たとえば、Substringは文字列の一部を取り出して、新しい文字列を作ります。

C#
string text = "Hello World";

string sub = text.Substring(0, 5);

この場合、subには"Hello"という新しい文字列が作られます。

一方、AsSpanは新しい文字列を作らず、元の文字列の一部を参照します。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan(0, 5);

この場合、spantextの先頭5文字を参照しているだけです。

そのため、短い文字列を数回処理する程度では大きな違いはありませんが、大量の文字列を繰り返し処理する場合は、メモリ使用量に差が出ることがあります。

2-4. ヒープ割り当てとパフォーマンスの関係

C#で新しい文字列やオブジェクトを作ると、多くの場合ヒープと呼ばれるメモリ領域に割り当てられます。

ヒープに多くのオブジェクトが作られると、ガベージコレクションの負荷が増える可能性があります。

Substringを大量に使うと、新しい文字列が何度も作られます。

C#
foreach (var line in lines)
{
string prefix = line.Substring(0, 5);
}

一方、AsSpanを使えば、新しい文字列を作らずに一部を参照できます。

C#
foreach (var line in lines)
{
ReadOnlySpan<char> prefix = line.AsSpan(0, 5);
}

このように、AsSpanはヒープ割り当てを減らし、メモリ効率を改善するために役立ちます。

3. C#におけるAsSpanの基本的な使い方

3-1. string.AsSpanの基本構文

文字列に対してAsSpanを使う基本構文は次のとおりです。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan();

この場合、文字列全体がReadOnlySpan<char>として扱われます。

ReadOnlySpan<char>は、文字列のようにインデックスで各文字にアクセスできます。

C#
string text = "Hello";

ReadOnlySpan<char> span = text.AsSpan();

Console.WriteLine(span[0]); // H
Console.WriteLine(span[1]); // e

ただし、ReadOnlySpan<char>stringそのものではありません。そのため、stringを受け取るメソッドにそのまま渡せない場合があります。

必要に応じて、ToString()で文字列に変換します。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan(0, 5);

string result = span.ToString();

Console.WriteLine(result); // Hello

3-2. 配列に対してAsSpanを使う方法

配列に対してAsSpanを使うと、Span<T>として扱えます。

C#
int[] numbers = { 1, 2, 3, 4, 5 };

Span<int> span = numbers.AsSpan();

配列の場合は、Span<T>を通じて値を変更できます。

C#
int[] numbers = { 1, 2, 3, 4, 5 };

Span<int> span = numbers.AsSpan();

span[0] = 100;

Console.WriteLine(numbers[0]); // 100

これは、spanが元の配列を参照しているためです。

配列の一部だけを処理したい場合にも便利です。

C#
int[] numbers = { 10, 20, 30, 40, 50 };

Span<int> middle = numbers.AsSpan(1, 3);

foreach (int n in middle)
{
Console.WriteLine(n);
}

出力結果は次のとおりです。

C#
20
30
40

3-3. 開始位置を指定してAsSpanを使う方法

AsSpanでは、開始位置を指定して、指定した位置から最後までを参照できます。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan(6);

Console.WriteLine(span.ToString()); // World

この例では、インデックス6から最後までを参照しています。

配列でも同じように使えます。

C#
int[] numbers = { 10, 20, 30, 40, 50 };

Span<int> span = numbers.AsSpan(2);

foreach (int n in span)
{
Console.WriteLine(n);
}

出力結果は次のとおりです。

C#
30
40
50

開始位置は0から始まるため、AsSpan(2)は3番目の要素から最後までを意味します。

3-4. 開始位置と長さを指定してAsSpanを使う方法

AsSpanでは、開始位置と長さを指定して範囲を切り出すこともできます。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan(0, 5);

Console.WriteLine(span.ToString()); // Hello

この例では、インデックス0から5文字分を参照しています。

配列の場合も同じです。

C#
int[] numbers = { 10, 20, 30, 40, 50 };

Span<int> span = numbers.AsSpan(1, 3);

foreach (int n in span)
{
Console.WriteLine(n);
}

出力結果は次のとおりです。

C#
20
30
40

なお、範囲外の開始位置や長さを指定すると例外が発生します。

C#
string text = "Hello";

// 範囲外なので例外
// ReadOnlySpan<char> span = text.AsSpan(2, 10);

3-5. AsSpanで取得した範囲を処理するサンプルコード

次の例では、文字列の先頭部分をAsSpanで取得し、条件判定に使っています。

C#
string log = "INFO: Application started";

ReadOnlySpan<char> level = log.AsSpan(0, 4);

if (level.SequenceEqual("INFO"))
{
Console.WriteLine("情報ログです");
}

SequenceEqualを使うことで、ReadOnlySpan<char>と文字列リテラルを比較できます。

次のように、配列の一部だけを合計する処理も書けます。

C#
int[] scores = { 80, 90, 70, 60, 100 };

Span<int> target = scores.AsSpan(1, 3);

int total = 0;

foreach (int score in target)
{
total += score;
}

Console.WriteLine(total); // 220

このように、AsSpanを使うと、元データの一部をコピーせずに処理できます。

4. AsSpanを使うメリット

4-1. 不要な文字列生成を避けられる

AsSpanの大きなメリットは、不要な文字列生成を避けられることです。

たとえば、次のようにSubstringを使うと、新しい文字列が作られます。

C#
string text = "ERROR: Something happened";

string level = text.Substring(0, 5);

一方、AsSpanを使うと、元の文字列を参照するだけです。

C#
string text = "ERROR: Something happened";

ReadOnlySpan<char> level = text.AsSpan(0, 5);

この違いは、小さなプログラムではあまり気にならないかもしれません。

しかし、大量の文字列を何度も処理する場合、不要な文字列生成を避けられることは大きなメリットになります。

4-2. Substringよりメモリ効率がよい

Substringは、取り出した部分文字列を新しいstringとして生成します。

C#
string text = "Hello World";

string sub = text.Substring(0, 5);

この場合、"Hello"という新しい文字列がメモリ上に作られます。

一方、AsSpanは元の文字列を参照します。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan(0, 5);

新しい文字列を生成しないため、メモリ割り当てを減らせます。

特に、ループの中で何度も文字列の一部を取り出すような処理では、AsSpanを使うことでメモリ効率がよくなります。

4-3. 大量データ処理でパフォーマンス改善が期待できる

AsSpanは、大量のデータを処理する場面で効果を発揮しやすいです。

たとえば、ログファイルの各行を解析する処理を考えます。

C#
foreach (string line in lines)
{
ReadOnlySpan<char> level = line.AsSpan(0, 5);

if (level.SequenceEqual("ERROR"))
{
Console.WriteLine(line);
}
}

このようにすれば、各行の先頭部分を新しい文字列として作らずに比較できます。

ログ解析、CSV解析、固定長データの読み取りなど、文字列の一部を何度も扱う処理では、AsSpanによってパフォーマンス改善が期待できます。

4-4. 配列や文字列の一部を安全に扱いやすい

AsSpanを使うと、配列や文字列の一部だけを明示的に扱えます。

C#
int[] values = { 1, 2, 3, 4, 5, 6 };

Span<int> part = values.AsSpan(2, 3);

この例では、valuesの中から3, 4, 5の部分だけをpartとして扱っています。

処理対象の範囲が明確になるため、配列全体を渡すよりも意図が分かりやすくなる場合があります。

C#
static int Sum(Span<int> values)
{
int total = 0;

foreach (int value in values)
{
total += value;
}

return total;
}

このようなメソッドを用意しておけば、配列全体にも一部にも同じ処理を適用できます。

4-5. 高速な文字列解析・パース処理に向いている

AsSpanは、文字列解析やパース処理に向いています。

たとえば、日付や数値、ログレベル、IDなど、文字列の一部を取り出して処理する場面で便利です。

C#
string input = "12345";

ReadOnlySpan<char> span = input.AsSpan();

if (int.TryParse(span, out int number))
{
Console.WriteLine(number);
}

int.TryParseReadOnlySpan<char>を受け取れるため、文字列をコピーせずに数値変換できます。

このように、AsSpanは高速な文字列解析を行いたいときに有効です。

5. AsSpanとSubstringの違い

5-1. Substringは新しい文字列を生成する

Substringは、文字列の一部を取り出して新しい文字列を作るメソッドです。

C#
string text = "Hello World";

string sub = text.Substring(0, 5);

Console.WriteLine(sub); // Hello

戻り値はstringです。

そのため、取り出した結果を通常の文字列として保存したり、他の文字列メソッドで扱ったりしやすいです。

ただし、新しい文字列を生成するため、メモリ割り当てが発生します。

5-2. AsSpanは元データを参照する

AsSpanは、元の文字列や配列の一部を参照します。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan(0, 5);

Console.WriteLine(span.ToString()); // Hello

AsSpan自体は、新しい文字列を作りません。

ただし、span.ToString()を呼び出すと、その時点で新しい文字列が作られます。

C#
string result = span.ToString();

つまり、AsSpanのメリットを活かすには、なるべくReadOnlySpan<char>のまま比較や解析を行うことが重要です。

5-3. メモリ使用量の違い

SubstringAsSpanでは、メモリ使用量に違いがあります。

Substringは新しい文字列を作るため、その分メモリを使用します。

C#
string sub = text.Substring(0, 5);

AsSpanは元の文字列を参照するだけなので、新しい文字列のメモリ割り当てを避けられます。

C#
ReadOnlySpan<char> span = text.AsSpan(0, 5);

特に、次のようなコードでは違いが出やすくなります。

C#
foreach (string line in lines)
{
string prefix = line.Substring(0, 5);
}

このコードでは、ループのたびに新しい文字列が作られます。

AsSpanを使うと、次のように書けます。

C#
foreach (string line in lines)
{
ReadOnlySpan<char> prefix = line.AsSpan(0, 5);
}

この場合、新しい文字列を生成しないため、メモリ効率がよくなります。

5-4. 処理速度の違い

AsSpanは、新しい文字列を作らないため、処理速度の面でも有利になることがあります。

ただし、必ずすべての場面で高速になるわけではありません。

たとえば、1回だけ短い文字列を切り出すような処理では、Substringでも十分です。

C#
string name = fullName.Substring(0, 5);

一方、大量の文字列を繰り返し処理する場合は、AsSpanの効果が出やすくなります。

C#
foreach (string line in lines)
{
if (line.AsSpan(0, 5).SequenceEqual("ERROR"))
{
Console.WriteLine(line);
}
}

処理速度を目的にAsSpanを使う場合は、実際に計測して効果を確認することが大切です。

5-5. 戻り値の型の違い

Substringの戻り値はstringです。

C#
string result = text.Substring(0, 5);

一方、string.AsSpanの戻り値はReadOnlySpan<char>です。

C#
ReadOnlySpan<char> result = text.AsSpan(0, 5);

配列に対するAsSpanの戻り値はSpan<T>です。

C#
int[] numbers = { 1, 2, 3 };

Span<int> span = numbers.AsSpan();

この戻り値の型の違いによって、使えるメソッドや保存できる場所が変わります。

stringとして長期間保持したい場合はSubstring、一時的に参照して処理したい場合はAsSpanが向いています。

5-6. 使い分けの判断基準

SubstringAsSpanは、目的によって使い分けるのが基本です。

取り出した文字列を保存したい、他の場所に渡したい、分かりやすさを優先したい場合はSubstringが適しています。

C#
string firstName = fullName.Substring(0, 5);

一方、文字列の一部を一時的に比較したい、解析したい、パースしたい場合はAsSpanが適しています。

C#
if (text.AsSpan(0, 5).SequenceEqual("ERROR"))
{
Console.WriteLine("エラーです");
}

初心者のうちは、まずSubstringで分かりやすく書き、パフォーマンスやメモリ効率が問題になった場面でAsSpanを検討するとよいでしょう。

6. AsSpanの実践的な使用例

6-1. 文字列の一部を切り出して比較する

AsSpanは、文字列の一部を比較する処理に向いています。

C#
string text = "ERROR: Disk full";

ReadOnlySpan<char> prefix = text.AsSpan(0, 5);

if (prefix.SequenceEqual("ERROR"))
{
Console.WriteLine("エラーが発生しました");
}

このコードでは、先頭5文字をReadOnlySpan<char>として参照し、"ERROR"と比較しています。

Substringを使う場合は次のようになります。

C#
if (text.Substring(0, 5) == "ERROR")
{
Console.WriteLine("エラーが発生しました");
}

Substringでも分かりやすく書けますが、新しい文字列が生成されます。

大量のログを処理する場合は、AsSpanを使うことで不要な文字列生成を避けられます。

6-2. CSVやログ文字列を解析する

CSVやログ文字列の解析でも、AsSpanは役立ちます。

次の例では、カンマ区切りの文字列から最初の項目を取り出しています。

C#
string csv = "1001,Tanaka,Tokyo";

int commaIndex = csv.IndexOf(',');

ReadOnlySpan<char> id = csv.AsSpan(0, commaIndex);

Console.WriteLine(id.ToString()); // 1001

この例では、最初のカンマの位置を見つけ、その前の部分をReadOnlySpan<char>として取得しています。

ログ文字列でも同じように使えます。

C#
string log = "WARN: Memory usage is high";

ReadOnlySpan<char> level = log.AsSpan(0, 4);

if (level.SequenceEqual("WARN"))
{
Console.WriteLine("警告ログです");
}

文字列の一部を一時的に調べるだけなら、AsSpanで十分です。

6-3. 数値文字列をSpanでパースする

ReadOnlySpan<char>は、数値のパースにも使えます。

C#
string text = "12345";

ReadOnlySpan<char> span = text.AsSpan();

if (int.TryParse(span, out int value))
{
Console.WriteLine(value);
}

文字列の一部だけを数値として変換することもできます。

C#
string text = "ID:12345";

ReadOnlySpan<char> numberPart = text.AsSpan(3, 5);

if (int.TryParse(numberPart, out int id))
{
Console.WriteLine(id); // 12345
}

Substringを使わずに数値部分をパースできるため、メモリ効率のよい処理になります。

6-4. 配列の一部だけを処理する

AsSpanは文字列だけでなく、配列にも使えます。

次の例では、配列の一部だけを合計しています。

C#
int[] numbers = { 10, 20, 30, 40, 50 };

Span<int> target = numbers.AsSpan(1, 3);

int total = 0;

foreach (int number in target)
{
total += number;
}

Console.WriteLine(total); // 90

numbers.AsSpan(1, 3)は、インデックス1から3つの要素を参照します。

つまり、対象は20, 30, 40です。

また、Span<T>を使うと、元の配列の一部を変更することもできます。

C#
int[] numbers = { 1, 2, 3, 4, 5 };

Span<int> part = numbers.AsSpan(1, 3);

for (int i = 0; i < part.Length; i++)
{
part[i] *= 10;
}

Console.WriteLine(string.Join(",", numbers)); // 1,20,30,40,5

このように、配列の一部分だけを処理したい場合にもAsSpanは便利です。

6-5. StartsWithやSequenceEqualと組み合わせる

ReadOnlySpan<char>には、比較に使える便利なメソッドがあります。

たとえば、StartsWithを使うと、指定した文字列で始まるかどうかを判定できます。

C#
string text = "ERROR: Network error";

ReadOnlySpan<char> span = text.AsSpan();

if (span.StartsWith("ERROR"))
{
Console.WriteLine("エラーです");
}

SequenceEqualを使うと、内容が一致しているかどうかを判定できます。

C#
string text = "OK";

ReadOnlySpan<char> span = text.AsSpan();

if (span.SequenceEqual("OK"))
{
Console.WriteLine("成功です");
}

AsSpanは、このようなSpan対応メソッドと組み合わせることで、効率的な文字列処理を書けます。

7. AsSpanを使うときの注意点

7-1. Spanは通常のstringとは異なる

AsSpanで取得できるReadOnlySpan<char>は、通常のstringとは異なります。

たとえば、次のようにstring型の変数へ直接代入することはできません。

C#
string text = "Hello World";

// string result = text.AsSpan(0, 5); // エラー

stringとして扱いたい場合は、ToString()を使う必要があります。

C#
string text = "Hello World";

string result = text.AsSpan(0, 5).ToString();

Console.WriteLine(result); // Hello

ただし、ToString()を呼び出すと新しい文字列が生成されます。

そのため、AsSpanのメリットを活かしたい場合は、可能な限りReadOnlySpan<char>のまま処理することが大切です。

7-2. ReadOnlySpan<char>はそのまま保存しにくい

ReadOnlySpan<char>は、通常のフィールドやプロパティに保存しにくい型です。

たとえば、クラスのフィールドとして保持しようとすると制限があります。

C#
class Sample
{
// private ReadOnlySpan<char> _span; // 通常のクラスではエラー
}

これは、Span<T>ReadOnlySpan<T>がメモリ安全性を守るために特別な制約を持っているためです。

ReadOnlySpan<char>は、長期間保存するというよりも、メソッド内で一時的に使うものと考えるとよいでしょう。

長く保持したい場合は、stringとして保存するのが基本です。

C#
string value = span.ToString();

7-3. 非同期処理やラムダ式で扱う際の制限

Span<T>ReadOnlySpan<T>は、非同期処理やラムダ式で扱うときに制限があります。

たとえば、asyncメソッドの中でReadOnlySpan<char>awaitをまたいで使うことはできない場合があります。

C#
async Task SampleAsync(string text)
{
ReadOnlySpan<char> span = text.AsSpan(0, 5);

// await Task.Delay(1000);

// awaitをまたいでspanを使うような書き方には注意が必要
}

これは、Span<T>がスタック上の安全性を重視した型だからです。

初心者のうちは、AsSpanはメソッド内の短い範囲で使うと考えておくと安全です。

7-4. 必要に応じてToStringで文字列化する

AsSpanで取得した値を、どうしてもstringとして使いたい場合はToString()を使います。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan(0, 5);

string result = span.ToString();

Console.WriteLine(result); // Hello

ただし、ToString()を呼び出すと新しい文字列が作られます。

つまり、次のように書くと、最終的にはSubstringと同じように文字列が生成されます。

C#
string result = text.AsSpan(0, 5).ToString();

そのため、文字列として保存する必要があるなら、最初からSubstringを使ったほうが分かりやすい場合もあります。

AsSpanは、文字列化せずに比較や解析を行うときに特に効果を発揮します。

7-5. 小規模な処理では効果を感じにくい場合がある

AsSpanはパフォーマンス改善に役立つことがありますが、すべての場面で効果を感じられるわけではありません。

たとえば、次のような短い処理では、Substringでも十分です。

C#
string text = "Hello World";

string result = text.Substring(0, 5);

1回だけ文字列を切り出す程度であれば、AsSpanを使っても体感できるほどの差は出にくいです。

むしろ、AsSpanを使うことでコードが分かりにくくなる場合もあります。

パフォーマンス改善を目的に使う場合は、実際の処理時間やメモリ使用量を計測して判断することが重要です。

8. AsSpanを使うべきケース・使わなくてもよいケース

8-1. AsSpanを使うべきケース

AsSpanを使うべきケースは、文字列や配列の一部を一時的に参照して処理したい場合です。

たとえば、次のようなケースではAsSpanが向いています。

ログファイルを大量に解析する場合、CSVや固定長データを処理する場合、文字列の一部を比較する場合、数値部分をパースする場合、配列の一部だけを処理する場合などです。

C#
string line = "ERROR: File not found";

if (line.AsSpan(0, 5).SequenceEqual("ERROR"))
{
Console.WriteLine("エラーです");
}

このような処理では、新しい文字列を作らずに済むため、メモリ効率がよくなります。

8-2. Substringで十分なケース

Substringで十分なケースも多くあります。

たとえば、切り出した文字列をその後も保存したい場合や、別のメソッドにstringとして渡したい場合です。

C#
string text = "Hello World";

string result = text.Substring(0, 5);

Save(result);

このような場合、最終的にstringが必要なので、Substringを使ったほうが分かりやすいです。

また、小規模な処理や、パフォーマンスが問題になっていない処理では、無理にAsSpanを使う必要はありません。

8-3. 可読性を優先したほうがよいケース

コードは、速さだけでなく読みやすさも重要です。

初心者やチームメンバーがSpan<T>に慣れていない場合、AsSpanを多用するとコードが分かりにくくなる可能性があります。

たとえば、次のコードはシンプルで分かりやすいです。

C#
string prefix = text.Substring(0, 5);

一方、次のコードはパフォーマンス面では有利な場合がありますが、ReadOnlySpan<char>に慣れていない人には少し分かりにくいかもしれません。

C#
ReadOnlySpan<char> prefix = text.AsSpan(0, 5);

パフォーマンス上の理由がないなら、可読性を優先してSubstringを使う判断も正しいです。

8-4. パフォーマンス改善を目的に使う場合の考え方

AsSpanをパフォーマンス改善のために使う場合は、まず本当にその処理がボトルネックになっているか確認することが大切です。

なんとなく速そうだから使うのではなく、次のような観点で判断するとよいでしょう。

文字列の切り出しが大量に発生しているか、ループ内でSubstringを何度も呼び出しているか、メモリ割り当てが多くなっていないか、ガベージコレクションの負荷が問題になっていないか。

たとえば、次のような処理ではAsSpanを検討する価値があります。

C#
foreach (string line in lines)
{
if (line.AsSpan(0, 5).SequenceEqual("ERROR"))
{
errorCount++;
}
}

一方、1回だけ文字列を切り出すような処理では、Substringで十分なことが多いです。

9. AsSpanに関するよくある質問

9-1. AsSpanは文字列をコピーしますか?

AsSpanは、基本的に文字列をコピーしません。

string.AsSpan()は、元の文字列をReadOnlySpan<char>として参照します。

C#
string text = "Hello World";

ReadOnlySpan<char> span = text.AsSpan(0, 5);

この時点では、"Hello"という新しい文字列は作られていません。

ただし、次のようにToString()を呼び出すと、新しい文字列が生成されます。

C#
string result = span.ToString();

つまり、AsSpan自体はコピーしませんが、ToString()すれば文字列がコピーされると考えると分かりやすいです。

9-2. AsSpanとToCharArrayの違いは何ですか?

AsSpanToCharArrayは、まったく異なる目的のメソッドです。

AsSpanは、元の文字列をコピーせずに参照します。

C#
string text = "Hello";

ReadOnlySpan<char> span = text.AsSpan();

一方、ToCharArrayは、文字列の内容を新しいchar[]配列としてコピーします。

C#
string text = "Hello";

char[] chars = text.ToCharArray();

ToCharArrayを使うと、新しい配列が作られるため、メモリ割り当てが発生します。

文字列を読み取るだけならAsSpan、文字の配列として変更したいならToCharArrayを使うとよいでしょう。

9-3. AsSpanは初心者でも使うべきですか?

初心者でもAsSpanを学ぶ価値はあります。

ただし、最初からすべての文字列処理でAsSpanを使う必要はありません。

まずは、Substringstringの基本的な扱いを理解したうえで、パフォーマンスやメモリ効率を意識する段階になったらAsSpanを使うとよいでしょう。

特に、次のような処理を書き始めたらAsSpanを検討できます。

C#
foreach (string line in lines)
{
string prefix = line.Substring(0, 5);
}

このように、ループ内で大量にSubstringを呼び出している場合は、AsSpanを使うことで改善できる可能性があります。

9-4. AsSpanはどのバージョンのC#で使えますか?

AsSpanは、C#の言語機能というより、.NETのライブラリ機能として提供されています。

近年の.NET環境では標準的に利用できます。たとえば、.NET Core 2.1以降や、現在の.NETでは一般的に使えます。

古い.NET Framework環境では、そのまま使えない場合があります。その場合は、System.Memoryパッケージが必要になることがあります。

また、Span<T>ReadOnlySpan<T>は特殊な制約を持つ型なので、利用するプロジェクトのターゲットフレームワークによって使える範囲が変わる場合があります。

実際に使うときは、自分のプロジェクトが対応している.NETバージョンを確認しましょう。

9-5. AsSpanを使えば必ず高速になりますか?

AsSpanを使えば必ず高速になるわけではありません。

AsSpanは、新しい文字列や配列の生成を避けられるため、メモリ効率の改善には役立ちます。

しかし、処理内容によっては差がほとんど出ない場合もあります。

たとえば、次のような単純な処理では、Substringでも十分です。

C#
string result = text.Substring(0, 5);

一方、大量の文字列を繰り返し処理する場合は、AsSpanの効果が出やすくなります。

C#
foreach (string line in lines)
{
if (line.AsSpan(0, 5).SequenceEqual("ERROR"))
{
errorCount++;
}
}

パフォーマンスを目的にする場合は、実際にベンチマークを取って確認することが大切です。

まとめ

C#のAsSpanは、文字列や配列をSpan<T>またはReadOnlySpan<T>として扱うためのメソッドです。

文字列に対して使うとReadOnlySpan<char>を取得でき、配列に対して使うとSpan<T>を取得できます。

AsSpanの大きな特徴は、元のデータをコピーせずに参照できることです。そのため、Substringのように新しい文字列を生成せず、メモリ効率のよい処理を書くことができます。

特に、ログ解析、CSV解析、数値文字列のパース、大量の文字列比較、配列の一部処理などでは、AsSpanが役立ちます。

一方で、AsSpanは通常のstringとは異なる型を返すため、扱いには注意が必要です。ReadOnlySpan<char>は長期間保存しにくく、非同期処理やラムダ式で制限を受ける場合もあります。

また、AsSpanを使えば必ず高速になるわけではありません。小規模な処理や、最終的にstringとして保存する処理では、Substringのほうが分かりやすく適している場合もあります。

初心者のうちは、まずSubstringとの違いを理解し、「一時的に文字列の一部を参照したいときはAsSpan」「新しい文字列として保持したいときはSubstring」と考えるとよいでしょう。

AsSpanを適切に使えるようになると、C#でより効率的な文字列処理や配列処理を書けるようになります。