Чтение значений из бинарного файла, типы которого известны во время выполнения

Я пытаюсь прочитать ряд значений из двоичного файла, но я не узнаю, какие типы значений существуют до времени выполнения.

Упрощенный пример

У меня есть двоичный файл длиной 10 байт. Байты представляют по порядку int, float и short. Я не знаю этого во время компиляции, но я знаю это во время выполнения с таким массивом:

        Type[] types = new Type[3];
        types[0] = typeof(int);
        types[1] = typeof(float);
        types[2] = typeof(short);

Вопрос

Итак, теперь, когда у меня есть этот список, могу ли я использовать эту информацию для быстрого чтения значений из файла? Единственный способ, который я могу придумать, это использовать большой блок if, но он выглядит очень уродливо:

        for (int i = 0; i < types.Length; i++)
        {
            if (types[i] == typeof(int))
            {
                int val = binaryfile.ReadInt32();
                //... etc ...
            }
            else if (types[i] == typeof(float))
            {
                float val = binaryfile.ReadSingle();
                //... etc ...
            }
            else if //... etc...
        }

Но это некрасиво и громоздко. Мне интересно, могу ли я использовать информацию Type в массиве types, чтобы как-то «автоматизировать» это.

Что я пробовал

Одна идея, о которой я подумал, заключалась в том, чтобы прочитать необработанные байты в массив, а затем выполнить преобразование в массиве байтов. Итак, скажем, мой массив выглядит так:

        byte[] buf = new byte[10] {
            0x40, 0xE2, 0x01, 0x00,
            0x79, 0xE9, 0xF6, 0x42,
            0x39, 0x30 };

Он содержит значения int, float и short 123456, 123,456 и 12345 соответственно. Теперь я могу сделать следующее:

        fixed (byte* bp = &buf[0])
        {
            int* ip = (int*)bp;
            Console.WriteLine("int ptr: {0}", *ip);
        }

Кажется, это работает хорошо, но есть две проблемы:

  1. Я не знаю, как маршалировать *ip обратно в управляемый домен.
  2. Я все еще не могу использовать свой список типов, как показано ниже:

        fixed (byte* bp = &buf[0])
        {
            (types[0])* ip = ((types[0])*)bp;      // both errors here
            Console.WriteLine("int ptr: {0}", *ip);
        }
    

Это приводит к двум ошибкам времени компиляции в указанной строке:

Error   1   Invalid expression term ')'
Error   2   ) expected

Это пока все, что я придумал попробовать.

Я надеюсь, что кто-то может помочь. Я чувствую, что упускаю что-то простое, что сделало бы мою жизнь намного проще.

Обновлять

Я попробовал предложение Питера Дунихо, и, похоже, оно работает достаточно хорошо, хотя по сравнению с большим блоком if наблюдается небольшое снижение производительности.

Вот некоторые результаты из файла ~ 100 МБ (все время указано в мс):

Метод Питера:

2025
2003
1954
1979
1958

if блок:

1531
1488
1486
1489

Ничего особенного, хотя, поскольку я планирую работать с гораздо большими файлами (в диапазоне ГБ), эти несколько сотен миллисекунд складываются, поэтому я буду придерживаться уродливого блока if, пока не найду что-то столь же быстрое.


person Reticulated Spline    schedule 06.11.2014    source источник


Ответы (1)


Я не уверен на 100%, что понимаю, какую часть этой проблемы вы на самом деле пытаетесь решить. Но, основываясь на том, что, я думаю, вы спрашиваете, я бы сделал это так:

class Program
{
    static readonly Dictionary<Type, Func<byte[], int, Tuple<object, int>>> _converters =
        new Dictionary<Type, Func<byte[], int, Tuple<object, int>>>
        {
            { typeof(int), (rgb, ib) =>
                Tuple.Create((object)BitConverter.ToInt32(rgb, ib), sizeof(int)) },
            { typeof(float), (rgb, ib) =>
                Tuple.Create((object)BitConverter.ToSingle(rgb, ib), sizeof(float)) },
            { typeof(short), (rgb, ib) =>
                Tuple.Create((object)BitConverter.ToInt16(rgb, ib), sizeof(short)) },
        };

    static void Main(string[] args)
    {
        Type[] typeMap = { typeof(int), typeof(float), typeof(short) };
        byte[] inputBuffer =
            { 0x40, 0xE2, 0x01, 0x00, 0x79, 0xE9, 0xF6, 0x42, 0x39, 0x30 };
        int ib = 0, objectIndex = 0;

        while (ib < inputBuffer.Length)
        {
            Tuple<object, int> current =
                _converters[typeMap[objectIndex++]](inputBuffer, ib);
            Console.WriteLine("Value: " + current.Item1);
            ib += current.Item2;
        }
    }
}
person Peter Duniho    schedule 06.11.2014
comment
Вау, это конечно... интересно, если не сказать больше! Мне потребовалось немного поработать над этим, чтобы точно понять, что происходит. Однако сколько накладных расходов это повлечет? Поиск по словарю, лямбда-функции, упаковка и распаковка — кажется, что это был бы вычислительно затратный процесс. Думаю, мне придется попробовать это самому, чтобы увидеть, как это работает по сравнению с использованием только блока if. - person Reticulated Spline; 07.11.2014
comment
Словарный поиск выполняется быстро. Анонимные методы несут накладные расходы на инициализацию типа, но в остальном они эффективны. Бокс здесь исключительно для демонстрационных целей; если у вас не было бокса в исходной реализации, вы сможете применить описанную выше технику, но без бокса (т.е. вызвать любую логику, которую вы делали раньше в условных блоках). Тем не менее, даже если вам пришлось иметь дело с боксом, это вряд ли будет проблемой в типичных сценариях ввода-вывода (файл, сеть и т. д.). - person Peter Duniho; 07.11.2014