Индексация в массивы произвольного ранга в C#

Мне нужно перебрать массив произвольного ранга. Это и для чтения, и для записи, поэтому GetEnumerator не сработает.

Array.SetValue(object, int) не работает с многомерными массивами. Array.SetValue(object, params int[]) потребовала бы чрезмерной арифметики для перебора многомерного пространства. Также потребуется динамический вызов, чтобы обойти часть params подписи.

У меня возникает соблазн закрепить массив и выполнить итерацию по нему с помощью указателя, но я не могу найти документацию, в которой говорится, что многомерные массивы гарантированно будут непрерывными. Если у них есть отступы в конце измерения, это не сработает. Я бы также предпочел избегать небезопасного кода.

Есть ли простой способ последовательно обращаться к многомерному массиву, используя только один индекс?


person Kennet Belenky    schedule 04.08.2010    source источник
comment
Я не вижу ошибки в использовании небезопасного кода. Использование указателей безопасно, когда вы действительно знаете, что делаете, и это жизнеспособное и эффективное решение.   -  person Mikael Svenson    schedule 04.08.2010
comment
Кстати, вполне допустимо передать int[] там, где ожидается params int[].   -  person dtb    schedule 04.08.2010
comment
@dtb, похоже, вы правы в обоих синтаксических пунктах. Я никогда не знал, что можно напрямую передать массив параметру массива params.   -  person Kennet Belenky    schedule 04.08.2010
comment
@ Микаэль Свенсон, я согласен с тем, что опасаться небезопасного кода не следует, но я также думаю, что его следует избегать, если есть эквивалентное безопасное решение.   -  person Kennet Belenky    schedule 04.08.2010


Ответы (3)


Многомерные массивы гарантированно непрерывны. Из ECMA-335:

Элементы массива должны располагаться внутри объекта массива в порядке строк (т. е. элементы, связанные с самым правым измерением массива, должны располагаться последовательно от низшего индекса к самому высокому).

Итак, это работает:

int[,,,] array = new int[10, 10, 10, 10];

fixed (int* ptr = array)
{
    ptr[10] = 42;
}

int result = array[0, 0, 1, 0];  // == 42
person dtb    schedule 04.08.2010
comment
Спасибо, это то, что я искал, и это дает мне хорошую резервную реализацию. Кстати, я думаю, что ваш код задыхается от «int * ptr = array», но идея здравая. - person Kennet Belenky; 04.08.2010
comment
@Kennet Belenky: я только что перепроверил, и код работает отлично, как есть. Конечно, решение, включающее небезопасный код, не поможет, если вы хотите избежать небезопасного кода, но я боюсь, что нет другого решения, которое не сопровождалось бы большими (ненужными) накладными расходами (такими как рекурсивная итерация по всем измерениям). . - person dtb; 04.08.2010
comment
... однако оказывается, что закрепление работает только в том случае, если массив содержит примитивные типы значений. Я не упомянул об этом в исходном вопросе, но массив, к которому я пытаюсь обратиться, также имеет произвольный ElementType и может содержать структуры, ссылочные типы или типы значений в штучной упаковке. Думаю, мне придется прибегнуть к использованию массива индексации. - person Kennet Belenky; 05.08.2010

Вы можете использовать свойство/метод Rank и GetUpperBound для создания массива индексов, который вы можете передать методам SetValue и GetValue массива:

int[] Indices(Array a, int idx)
{
    var indices = new int[a.Rank];

    for (var i = 0; i < a.Rank; i++)
    {
        var div = 1;

        for (var j = i + 1; j < a.Rank; j++)
        {
            div *= a.GetLength(j);
        }

        indices[i] = a.GetLowerBound(i) + idx / div % a.GetLength(i);
    }

    return indices;
}

..и используйте его так:

for (var i = 0; i < array.Length; i++)
{
    var indices = Indices(array, i);
    array.SetValue(i, indices);
    var val = array.GetValue(indices);
}
person Josef Pfleger    schedule 04.08.2010
comment
Да, это была бы чрезмерная арифметика, о которой я говорил. Вам также нужно будет использовать Array.GetLowerBound, а не только Array.GetUpperBound. - person Kennet Belenky; 04.08.2010
comment
@kennet На самом деле нам не нужен Array.GetUpperBound, просто увидел метод Array.GetLength :) - person Josef Pfleger; 05.08.2010

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

person Asmor    schedule 04.08.2010
comment
Достаточно просто, я просто использую GetEnumerator для адресации всего в массиве. Проблема в том, что мне также приходится писать в разных точках массива, а GetEnumerator для этого не работает. - person Kennet Belenky; 04.08.2010