Четене на стойности от двоичен файл, чиито типове са известни по време на изпълнение

Опитвам се да прочета поредица от стойности от двоичен файл, но няма да знам какви са типовете стойности до момента на изпълнение.

Опростен пример

Имам двоичен файл с дължина 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

Това е всичко, което съм мислил да опитам досега.

Надявам се някой да помогне. Имам чувството, че ми липсва нещо просто, което би направило живота ми много по-лесен.

Актуализация

Опитах предложението на Peter Duniho и изглежда, че работи доста добре, въпреки че има малък удар в производителността в сравнение с голям блок if.

Ето някои резултати от ~100 MB файл (всички времена са в ms):

Методът на Петър:

2025
2003
1954
1979
1958

if блок:

1531
1488
1486
1489

Нищо особено важно, въпреки че тъй като планирам да работя с много, много по-големи файлове (в диапазона GB), тези няколкостотин милисекунди се натрупват, така че ще се придържам към грозния блок 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
Търсенето в речника е бързо. Анонимните методи водят до излишни разходи за инициализация на типа, но иначе са ефективни. Боксът е там само за демонстрационни цели; ако не сте имали бокс в първоначалната си реализация, трябва да можете да приложите горната техника, но без бокса (т.е. извикайте каквато и да е логика, която сте правили преди в условните блокове). Въпреки това, дори и да трябва да се справите с бокс, това изобщо не е проблем в типичните I/O сценарии (файл, мрежа и т.н.). - person Peter Duniho; 07.11.2014