Полное руководство по языку программирования С# 14 и платформе .NET 10 / Коллекции

Span

Начальный ⏱ 5 мин чтения 👁 2 🕓 обновлено 11.08.2026

Тип Span представляет непрерывную область памяти. Цель данного типа - повысить производительность и эффективность использования памяти. Span позволяет избежать дополнительных выделений памяти при операции с наборами данных. Поскольку Span является структурой, то объект этого типа располагаетс в стеке, а не в хипе.

Создание Span

Для создания объекта Span можно применять один из его конструкторов:

  • Span(): создает пустой объект Span

  • Span(T item): создает объект Span с одним элементом item

  • Span(T[] array): создает объект Span из массива array

  • Span(void* pointer, int length): создает объект Span, который получает length байт памяти, начиная с указателя pointer

  • Span(T[] array, int start, int length): создает объект Span, который получает из массива array length элементов, начиная с индекса start

Например, простейшее создание Span:


Span<string> people = ["Tom", "Bob", "Sam"];

В данном случае Span будет хранить ссылки на три строки.

Нередко Span создается на основе каких-то других наборов данных:


string[] people =  { "Tom", "Alice", "Bob" };
Span<string> peopleSpan = new Span<string>(people);

Мы также можем непосредственно присвоить массив, и он неявно будет преобразован в Span:


string[] people = { "Tom", "Alice", "Bob" };
Span<string> peopleSpan = people;

Далее мы можем получать, устанавливать или перебирать данные также, как в массиве:


string[] people = { "Tom", "Alice", "Bob" };
Span<string> peopleSpan = people;
peopleSpan[1] = "Ann";              // переустановка значения элемента
Console.WriteLine(peopleSpan[2]);   // получение элемента
Console.WriteLine(peopleSpan.Length);   // получение длины Span

// перебор Span
foreach (var s in peopleSpan)
{
	Console.WriteLine(s);
}

Если Span ведет себя внешне как массив, то в чем его преимущество или когда он нам может пригодиться? Рассмотрим простейшую ситуацию - у нас есть массив со значениями дневных температур воздуха за месяц, и нам надо получить их него два набора - набор температур за первую декаду и за последнюю декаду. Используя массивы, мы могли бы сделать так:


int[] temperatures =
{
	10, 12, 13, 14, 15, 11, 13, 15, 16, 17,
	18, 16, 15, 16, 17, 14,  9,  8, 10, 11,
	12, 14, 15, 15, 16, 15, 13, 12, 12, 11
};
int[] firstDecade = new int[10];	// выделяем память для первой декады
int[] lastDecade = new int[10];		// выделяем память для второй декады
Array.Copy(temperatures, 0, firstDecade, 0, 10);	// копируем данные в первый массив
Array.Copy(temperatures, 20, lastDecade, 0, 10);	// копируем данные во второй массив

Для хранения данных создаются два дополнительных массива для дневных температур каждой декады. С помощью метода Array.Copy данные из исходного массива temperatures копируются в два остальных массива. Но суть в данном случае в том, что для обоих массивов мы вынуждены выделить память. То есть оба массива по сути содержат те же данные, что и temperatures, однако в отдельных частях памяти.

Span позволяет работать с памятью более эффективно и избежать ненужных выделений памяти. Так, используем вместо массивов Span:


int[] temperatures =
{
	10, 12, 13, 14, 15, 11, 13, 15, 16, 17,
	18, 16, 15, 16, 17, 14,  9,  8, 10, 11,
	12, 14, 15, 15, 16, 15, 13, 12, 12, 11
};
Span<int> temperaturesSpan = temperatures;

Span<int> firstDecade = temperaturesSpan.Slice(0, 10);	// нет выделения памяти под данные
Span<int> lastDecade = temperaturesSpan.Slice(20, 10);	// нет выделения памяти под данные

Для создания производных объектов Span применяется метод Slice, который из Spana выделяет часть и возвращает ее в виде другого объекта Span. Теперь объекты Span firstDecade и lastDecade работают с теми же данными, что и temperaturesSpan, а дополнительно память не выделяется. То есть во всех трех случаях мы работаем с тем же массивом temperatures. Мы даже можем в одном Span изменить данные, и данные изменятся в другом:


int[] temperatures =
{
	10, 12, 13, 14, 15, 11, 13, 15, 16, 17,
	18, 16, 15, 16, 17, 14,  9,  8, 10, 11,
	12, 14, 15, 15, 16, 15, 13, 12, 12, 11
};
Span<int> temperaturesSpan = temperatures;

Span<int> firstDecade = temperaturesSpan.Slice(0, 10);

temperaturesSpan[0] = 25; // меняем в temperatureSpan
Console.WriteLine(firstDecade[0]); //25

За счет чего это достигается? Для понимания работы Span можно обратиться к исходному коду типа. В частности, мы можем в нем увидеть следующее свойство:


public readonly ref struct Span<T>
{
	//....
	public ref T this[int index] { get { ... } }
	//....
}

Здесь мы видим, что индексатор возвращает ref-ссылку, благодаря чем мы получаем доступ непосредственно к объекту и можем его изменять.

В данном случае, конечно, преимущества от отсутствия выделения дополнительной памяти под хранение объектов минимальны. Но при более интенсивной работе с данными выигрыш в производительности неизбежно должен возрастать.

Методы Span

Основные методы Span:

  • void Fill(T value): заполняет все элементы Span значением value

  • T[] ToArray(): преобразует Span в массив

  • Span<T> Slice(int start, int length): выделяет из Span length элементов начиная с индекса start в виде другого Span

  • void Clear(): очищает Span

  • void CopyTo(Span<T> destination): копирует элементы текущего Span в другой Span

  • bool TryCopyTo(Span<T> destination): копирует элементы текущего Span в другой Span, но при этом также возвращает значение bool, которое указывает, удачно ли прошла операция копирования

ReadOnlySpan

Структура ReadOnlySpan аналогична Span, только предназначена для неизменяемых данных. Например:


string text = "hello, world";
string worldString = text.Substring(startIndex: 7, length: 5); 			 // есть выделение памяти под символы
ReadOnlySpan<char> worldSpan = text.AsSpan().Slice(start: 7, length: 5); // нет выделения памяти под символы
//worldSpan[0] = 'a'; // Нельзя изменить
Console.WriteLine(worldSpan[0]); // выводим первый символ

// перебор символов
foreach(var c in worldSpan)
{
	Console.Write(c);
}

В данном случае с помощью метода AsSpan() преобразуем строку в объект ReadOnlySpan<char> и затем выделяем из него диапазон символов "world". Поскольку ReadOnlySpan предназначен только для чтения, то соответственно мы не можем изменить через него данные, но получить можем. В остальном работа с ReadOnlySpan идет так же, как с Span.

Ограничения Span

Как структура, определенная с модификатором ref, Span имеет ряд ограничений: она не может быть присвоена переменной типа Object, dynamic или переменной типа интерфейса. Она не может быть полем в объекте ссылочного типа (а только внутри ref-структур). Она не может использоваться в пределах операций await или yield.

Проверь себя

📋 Тест по теме «Span» Тест по теме

Практические задания

Лёгкое Ручная проверка Заполните пропуск в коде (1)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

_____<string> people = ["Tom", "Bob", "Sam"];
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (2)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

string[] people =  { "_____", "Alice", "Bob" };
Span<string> peopleSpan = new Span<string>(people);
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (3)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

string[] people = { "Tom", "_____", "Bob" };
Span<string> peopleSpan = people;
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (4)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

string[] people = { "Tom", "Alice", "_____" };
Span<string> peopleSpan = people;
peopleSpan[1] = "Ann";              // переустановка значения элемента
Console.WriteLine(peopleSpan[2]);   // получение элемента
Console.WriteLine(peopleSpan.Length);   // получение длины Span

// перебор Span
foreach (var s in peopleSpan)
{
	Console.WriteLine(s);
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (5)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

int[] temperatures =
{
	10, 12, 13, _____, 15, 11, 13, 15, 16, 17,
	18, 16, 15, 16, 17, 14,  9,  8, 10, 11,
	12, 14, 15, 15, 16, 15, 13, 12, 12, 11
};
int[] firstDecade = new int[10];	// выделяем память для первой декады
int[] lastDecade = new int[10];		// выделяем память для второй декады
Array.Copy(temperatures, 0, firstDecade, 0, 10);	// копируем данные в первый массив
Array.Copy(temperatures, 20, lastDecade, 0, 10);	// копируем данные во второй массив
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (1)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

_____<string> people = ["_____", "Bob", "Sam"];
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (2)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

string[] people =  { "Tom", "Alice", "Bob" };
_____<string> peopleSpan = _____ Span<string>(people);
Войдите, чтобы отправить решение
Среднее Ручная проверка Расставьте строки кода в правильном порядке
+20 XP

Строки кода из примера этого урока перемешаны. Впишите их в правильном порядке, каждую строку — с новой строки.

	//....
}
public readonly ref struct Span<T>
	public ref T this[int index] { get { ... } }
	//....
{
Войдите, чтобы отправить решение
Сложное Ручная проверка Объясните и модифицируйте пример из урока «Span»
+30 XP

Опишите своими словами, что делает приведённый пример кода из урока «Span», и предложите одно осмысленное изменение (например, другое входное значение или дополнительное условие). Приведите итоговый код.

Span<string> people = ["Tom", "Bob", "Sam"];
Войдите, чтобы отправить решение
Сложное Ручная проверка Найдите потенциальную проблему в коде урока «Span»
+30 XP

Изучите приведённый фрагмент кода и опишите, при каких входных данных или условиях он может сработать не так, как ожидается (граничный случай, отсутствие проверки и т.п.). Предложите исправленный вариант.

string[] people =  { "Tom", "Alice", "Bob" };
Span<string> peopleSpan = new Span<string>(people);
Войдите, чтобы отправить решение
Мини-проект Ручная проверка Мини-проект по теме «Span»
+50 XP

Соберите небольшую программу, которая последовательно демонстрирует ключевые идеи урока. Программа должна быть рабочей и содержать минимум два самостоятельно написанных фрагмента, а не просто копию примера из урока.

Войдите, чтобы отправить решение
Домашнее задание Ручная проверка Домашнее задание по теме «Span»
+25 XP

Напишите собственную небольшую программу, применяющую материал темы «Span». Используйте не менее одной конструкции, показанной в примерах урока. Прикрепите код решения и краткое описание того, что он делает.

Войдите, чтобы отправить решение

Обсуждение (0)

Войдите, чтобы оставить комментарий.