C#Hive: Projects & Progress | Программирование
1.95K subscribers
153 photos
17 videos
1 file
143 links
Сообщество единомышленников C#: решаем задачи, учимся, развиваемся и общаемся вместе. Советы по работе на фрилансе, готовые проекты, код ревью, рекомендации и исследования.

Вопросы/сотрудничество: @tel_phil9
Download Telegram
🖥 Span<T>: что это такое?

Тип Span представляет непрерывную область памяти. Цель данного типа — повысить производительность и эффективность использования памяти. 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 имеет ряд ограничений:
Не может быть присвоена переменной типа Object, dynamic или переменной типа интерфейса;
Не может быть полем в объекте ссылочного типа (а только внутри ref-структур);
Не может использоваться в пределах операций await или yield.

➡️ ReadOnlySpan
Структура 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
👍51
🖥 Span и многомерные массивы

Мы уже знаем, что из себя представляет структура 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
👍31