Start Debugging
RU
2026-06-20 Обновлено 2026-11-02 csharpdotnetperformancememory Edit on GitHub

Что такое Span<T> в C# и когда он действительно ускоряет ваш код?

Span<T> помогает избегать временных копий и выделений памяти при разборе и форматировании. Время жизни, владение и измерение производительности.

На этом сайте ИИ-агент Walter используется для исследования тем, написания материалов и обслуживания. Подготовка и проверка материалов

Span<T> — представление непрерывной памяти фиксированной длины: массива, стекового буфера или неуправляемой памяти с корректно обеспеченным временем жизни. ReadOnlySpan<T> допускает только чтение через представление. Строки предоставляют ReadOnlySpan<char>, а не изменяемый Span<char>.

Создание представления само по себе не выделяет и не передаёт владение исходным хранилищем. Это не делает всю операцию свободной от выделений памяти и не гарантирует ускорение.

Исправление — 2 ноября 2026 года: удалены неподтверждённые замеры и обещание универсального ускорения. Исправлены тип UTF-8-литерала, ограничения возврата и описание выделений памяти. Примеры проверены с SDK 10.0.302 и C# 14 без бенчмарка производительности.

Избежать временных подстрок

Парсеры, принимающие Span, могут исключить промежуточные строки. Этот пример разбирает точный формат без отдельных подстрок года, месяца и дня:

using System;
using System.Globalization;

string input = "2026-06-01";
DateOnly date = ParseIsoDate(input.AsSpan());
Console.WriteLine(date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));

static DateOnly ParseIsoDate(ReadOnlySpan<char> input) =>
    DateOnly.ParseExact(input, "yyyy-MM-dd", CultureInfo.InvariantCulture);

ParseExact проверяет дату и выбрасывает исключение при неверном вводе. Для ожидаемых ошибок используйте TryParseExact. Отдельные вызовы int.Parse(input.Slice(...)) сами по себе не проверяют разделители и календарную корректность.

Ограниченный временный буфер

using System;
using System.Globalization;

Span<char> scratch = stackalloc char[32];
if (12345.TryFormat(scratch, out int written, provider: CultureInfo.InvariantCulture))
{
    ReadOnlySpan<char> formatted = scratch[..written];
    Console.Out.WriteLine(formatted); // 12345
    string owned = new string(formatted); // Allocates the resulting string.
    Console.WriteLine(owned);
}

ReadOnlySpan<byte> method = "GET"u8;
Console.WriteLine(method.SequenceEqual(new byte[] { 71, 69, 84 })); // True

Буфер исключает промежуточный массив символов, но создание итоговой string всё ещё выделяет память. Массив байтов для сравнения тоже выделяется: он поясняет значения и не является бенчмарком. "GET"u8 имеет тип ReadOnlySpan<byte>.

Используйте небольшой ограниченный stackalloc. Большие или повторные выделения в цикле могут исчерпать стек. Не берите размер из непроверенного запроса; для больших данных нужны массивы или правильно управляемые буферы из пула.

Общая память и время жизни

Срезы не копируют элементы. Span на массив можно вернуть, если память остаётся действительной; Span на локальный стековый буфер не может покинуть метод. Поля Span допустимы в другом ref struct, но не в обычном классе или структуре. С C# 13 ref-struct-локальные переменные допустимы в async-методах, если не пересекают await; для пересечения используйте Memory<T>.

ReadOnlySpan<T> не замораживает буфер. При использовании CollectionsMarshal.AsSpan(list) нельзя добавлять и удалять элементы, даже при свободной ёмкости списка.

Измерять конкретное улучшение

Ищите устранимые копии и выделения: подстроки, скопированные срезы, временные массивы форматирования. Обычный цикл по массиву или списку уже может быть хорошо оптимизирован. Устранение проверок границ и скорость зависят от runtime и нагрузки; фиксированный множитель ненадёжен.

Измеряйте эквивалентную работу в Release на развёрнутом runtime с характерными данными. Вынесите подготовку из измерения, учитывайте выделения и сохраняйте код с выводом. Новые замеры скорости здесь не приводятся.

Базовые неявные преобразования массивов в Span существовали до C# 14. C# 14 вышел с .NET 10 и расширил языковую интеграцию. allows ref struct из C# 13 применим лишь к явно поддерживающим его обобщённым API.

Для выбора владения см. List, Span и ReadOnlySpan; для async-хранения — ReadOnlyMemory из массива.

Источники

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.