0
votes

I'm trying to serialize multidimensional arrays with any number of dimensions. Note that I'm referring to actual multidimensional arrays (float[,]), not jagged arrays (float[][]).

After serializing the length of each dimension, I can easily serialize each element using foreach (var item in array).

But when deserializing I can't use foreach because I need to assign the values. I know that I'll need to use array.SetValue(object value, params int[] indices), but I can't wrap my head around how to set up a loop to do what you would normally do with one nested for loop for each dimension.

1
Do you care about the order, or you just need all the values inside the array ? - Zein Makki
Yes, I need to read everything out of a BinaryReader to get the exact same array that was serialized initially. - SilentSin
Using BinaryFormatter is not an option? - Jeroen Heier
There's no such thing as an "array of unknown rank". An array object can return its rank (i.e. the Rank property), and correctly serialized data representing an array will contain information within to tell you this information so you can allocate an appropriately-sized array. Please provide a good minimal reproducible example that clearly illustrates your scenario and explain exactly what you've tried and why that didn't work. Without that information, there are just far too many possibilities for this question to be useful or for it to be clear what answer you need. - Peter Duniho
@PeterDuniho how many possibilities have you thought of for what "array of unknown rank" could mean? I can only come up with 2: either "unknown" refers to "unknown at compile-time", or OP doesn't know what they're talking about. If you look down you'll see that someone has already managed to pick the more useful possibility and provided a correct answer to the question. - SilentSin

1 Answers

1
votes

First, serialize the original array using a foreach loop. It will flatten the array.

Then, read the values in the same way the were serialized (code is based on the array's enumerator)

var original = new int[2,2,2] { { { 1, 2 }, { 3, 4} }, { { 5, 6 }, { 7, 8 } } };

var serialized = original.Cast<int>().ToArray();
var originalBounds = Enumerable.Range(0, original.Rank)
    .Select(i => original.GetUpperBound(i) + 1).ToArray();

var empty = Array.CreateInstance(typeof(int), originalBounds);

var indices = new int[empty.Rank];
indices[indices.Length - 1]--;
var index = 0;
while (IncArray(empty, indices))
{
    empty.SetValue(serialized[index++], indices);
}

private bool IncArray(Array array, int[] indices)
{
    int rank = array.Rank;
    indices[rank - 1]++;
    for (int i = rank - 1; i >= 0; i--)
    {
        if (indices[i] > array.GetUpperBound(i))
        {
            if (i == 0)
            {
                return false;
            }
            for (int j = i; j < rank; j++)
            {
                indices[j] = 0;
            }
            indices[i - 1]++;
        }
    }
    return true;
}

This approach would allow you to read the values from a stream - you don't need to create the serialized array like I have.

Another way is to use Buffer.BulkCopy. Here you'd have to read the entire array, at least into bytes, and calculate the amount of bytes you need to copy. The advantage here is that you avoid all the boxing in the previous method, and that it's simpler:

var bytes = new byte[...]; // read the entire byte array

var empty = Array.CreateInstance(typeof(int), originalBounds);

Buffer.BlockCopy(bytes, 0, empty, 0, bytes.Length);