C# Разделить массив байт без выделения памяти
Есть readonly byte[], необходимо разделить его на два массива без выделения новой памяти. Как я сделал бы это на С++:
Создал один пойнтер на начало массива с размерностью в половину исходного.
Создал второй пойнтер с оффсетом в размерность предыдущего и размерностью до конца исходного.
Всё, я просто переразметил память без перезаписи и выделения новой. Примерно так:
public unsafe void Divide(ref byte[] data, out byte[] secondHalf)
{
byte* fistHalf = byte[data.Length / 2];
byte* secondHalf = byte[data.Length / 2];
fistHalf = &data;
secondHalf = &data + data.Length / 2;
}
(очень приближённо, я ещё не понял, как указатели работают в C#)
Как это сделать в С# мне не очевидно совсем. Нашёл, что в С# тоже можно работать с указателями в unsafe, но это мове тон.
Ответы (2 шт):
Воспользуйтесь Span. примерно так:
int[] data = new int[]
{
//
};
Span<int> spanFromData = data;
Span<int> first = spanFromData.Slice(0, 10);
Span<int> last = spanFromData.Slice(10, 20);
В этом случае не будет выделения памяти. ReadOnlySpan предназначен для неизменяющихся данных
Разделить массив байт без выделения памяти
Это невозможно, но без этого можно прекрасно обойтись.
Массив - это некий класс в памяти, который помимо данных содержит еще информацию о длине массива, типе хранимых данных и другую служебную информацию. Вы хотите из одного класса экземпляра класса получить два, при этом чтобы данные остались на месте. Но ведь в месте разделения данных надо записать служебную информацию для второго массива, что в любом случае приведет к копированию данных.
Далее, о том, где взять память для храния данных в C# решает Garbage Collector. Он хорошо оптимизирован, и ему стоит доверять в плане выделения и освобождения памяти.
Для ручной работы с памятью есть unsafe код, и он кстати - не моветон, его просто не стоит использовать без причины, так как это может ухудшить поддерживаемость кода, да и смысл наличия GC при этом теряется. Но бывают случаи, когда unsafe реально оправдан, например при использовании векторных вычислений (intrinsics), хотя разработчики .NET извернулись и здесь, и вполне можно обойтись без unsafe без потерь в производительности, хоть и не во всех ситуациях.
Если ваша задача в том, чтобы обрабатывать массив по частям, то подойдет решение ниже.
Во-первых, писать байты в служебную область массива вам не позволит CLR, потому что она бережно защищает ваши данные и память от повреждения. Как можно выехать из данной ситуации.
К примеру у вас есть массив байт.
byte[] data = Enumerable.Range(0, 10).ToArray(); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
И вы хотите обработать сначала первую часть этого массива, потом вторую и при этом не копируя данные. Метод стар как мир.
private void DoSomething(byte[] array, int start, int length)
{
for (int i = start; i < start + length; i++)
{
array[i] = array[i] * 2;
}
}
И вызвать
DoSomething(data, 0, 5);
DoSomething(data, 5, 5);
Console.WriteLine(string.Join(", ", data)); // 0, 2, 4, 6, 8, 10, 12, 14, 16, 18
То есть вызываемый метод вносит изменения прямо в оригинальный массив, а не вкакую-то свою копию. Обратите внимание, я даже ref не использую.
Никаких указателей не требуется, так как массив - это и есть ссылка на данные в куче. Никакого копирования данных при выполнении выше указанного кода не произойдет.
Кстати, ref - это для значимых типов имеет смысл. Значимая часть в массиве - это сама ссылка на массив, то есть переменная, которая на него ссылается.