Span and two dimensional Arrays

You can create a Span with unmanaged memory. This will allow you to Slice and Dice indiscriminately.

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

Full Demo

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);

   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);
   }
}

Output

7
8
9
9.5
10

Other options would be to reallocate to a single dimension array, cop the penalty and do not Pass-Go

  • BlockCopy
  • or p/invoke memcpy directly and use unsafe and pointers
  • Cast<T> eg multiDimensionalArrayData.Cast<byte>().ToArray()

The first 2 will be more performant for large arrays.


All spans are one-dimensional because memory is one-dimensional.

You can of course map all manner of structures onto one-dimensional memory, but the Span class won't do it for you. But you could easily write something yourself, for example:

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;
        }
    }
}

The tricky part is implementing Slice(), since the semantics are sort of ambiguous for a two-dimensional structure. You can probably only slice this sort of structure by one of the dimensions, since slicing it by the other dimension would result in memory that is not contiguous.


You can use the new MemoryMarshal.CreateSpan and MemoryMarshal.GetArrayDataReference for this.

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

dotnetfiddle

This works on all dimensions of arrays. If you want generic type inference to work, you will need separate functions for every rank (level of dimension), such as AsSpan<T>(this T[,] array) and AsSpan<T>(this T[,,] array).


As John Wu already mentioned spans are one dimensional. You could of course implement a 2D span yourself, but Microsoft already did that for us.

Have a look into the docs here.
You can find the addressing nuget package here.
The package also provides a 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,..]);