Span и двумерные массивы

Можно ли использовать новую структуру Span System.Memory с двумерными массивами данных?

double[,] testMulti = 
    {
        { 1, 2, 3, 4 },
        { 5, 6, 7, 8 },
        { 9, 9.5f, 10, 11 },
        { 12, 13, 14.3f, 15 }
    };

double[] testArray = { 1, 2, 3, 4 };
string testString = "Hellow world";

testMulti.AsSpan(); // Compile error
testArray.AsSpan();
testString.AsSpan();

Хотя testArray и testString имеют расширение AsSpan, такого расширения для testMulti не существует.

Ограничен ли дизайн Span работой с одномерными массивами данных?
Я не нашел очевидного способа работы с массивом testMulti с использованием Span.

6 ответов

Решение

Вы можете создать Span с неуправляемой памятью. Это позволит вам нарезать и нарезать кубиками без разбора.

unsafe
{
    Span<T> something = new Span<T>(pointerToarray, someLength); 
}

Полная демонстрация

unsafe public static void Main(string[] args)
{
   double[,] doubles =  {
         { 1, 2, 3, 4 },
         { 5, 6, 7, 8 },
         { 9, 9.5f, 10, 11 },
         { 12, 13, 14.3f, 15 }
      };

   var length = doubles.GetLength(0) * doubles.GetLength(1) * sizeof(double);

   fixed (double* p = doubles)
   {
      var span = new Span<double>(p, length);
      var slice = span.Slice(6, 5);

      foreach (var item in slice)
         Console.WriteLine(item);
   }
}

Выход

7
8
9
9.5
10

Другими вариантами было бы перераспределение в один массив измерений, копирование штрафа и не Pass-Go

  • BlockCopy
  • или p/invoke memcpy напрямую и использовать unsafe и указатели
  • Cast<T> например multiDimensionalArrayData.Cast<byte>().ToArray()

Первые 2 будут более производительными для больших массивов.

Вы можете использовать новыйMemoryMarshal.CreateSpanиMemoryMarshal.GetArrayDataReferenceдля этого.

      public static Span<T> AsSpan<T>(this Array array)
{
    return MemoryMarshal.CreateSpan(ref Unsafe.As<byte, T>(ref MemoryMarshal.GetArrayDataReference(array)), array.Length);
}

дотнетфиддл

Это работает для всех измерений массивов. Если вы хотите, чтобы вывод универсального типа работал, вам потребуются отдельные функции для каждого ранга (уровня измерения), такие какAsSpan<T>(this T[,] array)иAsSpan<T>(this T[,,] array).

Как уже упоминал Джон Ву, пролеты одномерны. Конечно, вы могли бы реализовать 2D-диапазон самостоятельно, но Microsoft уже сделала это за нас.

Загляните в документацию здесь .
Вы можете найти пакет адресации nuget здесь .
Пакет также предоставляет Memory2D.

       var arr = new int[,] { {1,2,3},{2,2,3},{3,2,3} };
 var spn = arr.AsSapn2D();
 // Now use it similar to a normal span
 // The access is of course a bit different since we are using a 2D data structure.
 Console.WriteLine(spn[0..2,..]);

Все промежутки одномерны, потому что память одномерна.

Конечно, вы можете отобразить все виды структур на одномерную память, но класс Span не сделает этого за вас. Но вы можете легко написать что-нибудь сами, например:

public class Span2D<T> where T : struct
{
    protected readonly Span<T> _span;
    protected readonly int _width;
    protected readonly int _height;

    public Span2D(int height, int width)
    {
        T[] array = new T[_height * _width];
        _span = array.AsSpan();
    }

    public T this[int row, int column]
    {
        get
        {
            return _span[row * _height + column];
        }
        set
        {
            _span[row * _height + column] = value;
        }
    }
}

Сложная часть реализации Slice(), поскольку семантика является своего рода неоднозначной для двумерной структуры. Вероятно, вы можете разрезать структуру такого типа только по одному из измерений, так как разрезание по другому измерению приведет к тому, что память не будет смежной.

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

double[][] testMulti = 
    {
        new double[] { 1, 2, 3, 4 },
        new double[] { 5, 6, 7, 8 },
        new double[] { 9, 9.5f, 10, 11 },
        new double[] { 12, 13, 14.3f, 15 }
    };

Span<double[]> span = testMulti.AsSpan(2, 1);
Span<double> slice = span[0].AsSpan(1, 2);

foreach (double d in slice)
    Console.WriteLine(d);

slice[0] = 10.5f;

Console.Write(string.Join(", ", testMulti[2]));

Console.ReadLine();

ВЫХОД

9.5
10
9, 10.5, 10, 11

Как @saruman, я не верю, что это возможно.

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

Другие вопросы по тегам