Тип Span представляет непрерывную область памяти. Цель данного типа — повысить производительность и эффективность использования памяти. Span позволяет избежать дополнительных выделений памяти при операции с наборами данных. Поскольку Span является структурой, то объект этого типа располагается в стеке, а не в куче.
Для создания объекта можно использовать конструктор:
string[] person = { "Roman", "Alex", "Элла" };
Span<string> personSpan = new(person);
Также можно непосредственно присвоить массив, и он будет неявно преобразован в Span:
string[] person = { "Roman", "Alex", "Элла" };
Span<string> personSpan = person;
В обоих случаях, Span будет хранить ссылки на три строки. Далее мы можем получать, устанавливать или перебирать данные также, как в массиве:
string[] person = { "Roman", "Alex", "Элла" };
Span<string> personSpan = person;
personSpan[1] = "Drake";
Console.WriteLine(person[1]); // Drake
Console.WriteLine(personSpan[1]); // Drake
foreach (var p in personSpan)
{
Console.WriteLine(p);
}
Чтобы понять «где» и «когда» нам может пригодиться данный тип, нужно рассмотреть его на примере. Допустим, есть массив, хранящий количество сделок за три недели и нам нужно получить из него два набора — количество сделок за первую и последнюю неделю. Используя массивы, мы бы могли сделать так:
int[] ordersAmount =
{
3, 1, 5, 7, 4, 4, 0,
1, 0, 4, 11, 8, 9, 5,
9, 14, 7, 4, 8, 22, 3
};
int[] firstWeek = new int[7]; // выделяем память для первой недели
int[] lastWeek = new int[7]; // выделяем память для третьей недели
Array.Copy(ordersAmount, 0, firstWeek, 0, 7); // копируем данные в первый массив
Array.Copy(ordersAmount, 14, lastWeek, 0, 7); // копируем данные во второй массив
Для обоих массивов мы вынуждены выделить память, хотя оба массива, по сути, содержат те же данные, что и ordersAmount, но в отдельных частях памяти. Span позволяет работать с памятью более эффективно и избежать ненужных выделений памяти. Так, используем его вместо массивов:
int[] ordersAmount =
{
3, 1, 5, 7, 4, 4, 0,
1, 0, 4, 11, 8, 9, 5,
9, 14, 7, 4, 8, 22, 3
};
Span<int> spanOrdersAmount = ordersAmount;
Span<int> firstWeek = spanOrdersAmount.Slice(0, 7); // нет выделения памяти под данные
Span<int> lastWeek = spanOrdersAmount.Slice(14, 7); // нет выделения памяти под данные
Span имеет ряд ограничений:
Структура ReadOnlySpan аналогична Span, только предназначена для неизменяемых данных. Например:
string text = "hello, world";
string worldString = text.Substring(7, 5); // есть выделение памяти под символы
ReadOnlySpan<char> worldSpan = text.AsSpan().Slice(7, 5); // нет выделения памяти под символы
//worldSpan[0] = 'a'; // Нельзя изменить
Console.WriteLine(worldSpan[0]); // выводим первый символ
С помощью метода AsSpan() преобразуем строку в объект ReadOnlySpan<char> и затем выделяем из него диапазон символов. Поскольку ReadOnlySpan предназначен только для чтения, то, соответственно, мы не можем изменить через него данные, но можем получить. В остальном работа с ReadOnlySpan аналогична Span`у.
#Полезно #Span #ReadOnlySpan #Array
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤1
Мы уже знаем, что из себя представляет структура Span, позволяющая эффективно работать с памятью. Однако явным образом структура принимает только одномерные массивы.
Если мы попробуем написать следующее для двухмерного массива, то результат, который мы ожидаем, получен не будет:
int[,] array = { };
Span<int> span1 = array; // Ошибка компиляции
Span<int> span2 = array.AsSpan(); // Ошибка компиляции
Однако один из перегруженных конструкторов структуры Span имеет возможность указания по указателю, благодаря чему мы можем написать следующий метод:
unsafe Span<int> AsSpan(int[,] matrix)
{
fixed (int* p = matrix) return new Span<int>(p, matrix.Length);
}
Таким подходом двухмерный массив, как и любой другой многомерный массив, сможет теперь управляться Span`ом. Используем его:
int[,] arr = { { 1, 2 }, { 3, 4 } };
Console.WriteLine($"{arr[0, 0]} {arr[0, 1]} {arr[1, 0]} {arr[1, 1]}");
Span<int> span = AsSpan(arr);
span[0] = 0;
span[3] = 0;
Console.WriteLine($"{arr[0, 0]} {arr[0, 1]} {arr[1, 0]} {arr[1, 1]}");
Вывод:
1 2 3 4
0 2 3 0
Минус такого подхода в том, что Span работает с непрерывной областью памяти, а это может быть не очень удобно при попытке выделить определённые элементы, ведь они в Span будут предоставлены последовательно:
int[,] arr = { { 55, 12 }, { 97, 0 } };
Span<int> span = AsSpan(arr); // [55, 12, 97, 0]
Мы можем выделить промежуток элементов с 1 по 2 или с 2 по 4, но выделить только 1 и 4 элемент, без выделения дополнительной памяти, у нас не получится. Тем не менее, Span этим и прекрасен.
А чтобы из матрицы (двухмерного массива) выделить все элементы N-ой строки, мы можем написать нечто следующее:
unsafe Span<int> AsRowSpan(int[,] matrix, int indexRow)
{
fixed (int* p = matrix)
{
var span = new Span<int>(p, matrix.Length);
int columns = matrix.GetLength(1);
int start = indexRow * columns;
return span.Slice(start, columns);
}
}
Применим:
int[,] array2D = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Span<int> span = AsRowSpan(array2D, 1); // [4, 5, 6]
span.Fill(-1);
// Теперь array2D = [1, 2, 3, -1, -1, -1, 7, 8, 9]
Либо же использовать существующее решение, приводящее к аналогичному результату:
int[,] array2D = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Span<int> span = MemoryMarshal.CreateSpan(ref array2D[1, 0], array2D.GetLength(1));
#Полезно #Span #Array #Unsafe #Pointers
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1