В C # как я могу преобразовать байтовый массив двоичных данных в объект пользовательского типа, который моделирует данные? - PullRequest
4 голосов
/ 31 августа 2011

Сценарий: я получил необработанные двоичные данные через HTTP и сохранил данные в байтовом массиве.У меня есть документация, которая описывает различные поля, которые могут представлять двоичные данные, но фактическое значение данных должно быть определено во время выполнения.Например, если байт, представляющий возникновение ошибки = 1, то значение следующего байта изменяется.

Используя C # с .NET 4, я хочу создать один или несколько классов, которые отражают поляописано в документации, а затем каким-то образом инициализировать классы, используя байтовый массив двоичных данных.Мне бы хотелось, чтобы решение минимизировало дублирование кода, а также было модульным и элегантным.

Я рассмотрел создание Serializable классов, но я не понимаю, как это может работать, поскольку я начинаю с байтового массива, которыйне был создан (и, следовательно, не сериализован) мной.

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

Будем весьма благодарны за любые советы или указания по разработке расширяемого развязанного решения этой проблемы.

Редактировать:Пример классов, содержащих поля, которые отражают поля в спецификации

public class PriceHistoryResponse : BinaryResponse
{
    public List<Quote> quotes { get; set; }
    private CountData countData { get; set; }
    private EndingDelimiterSection endingDelimiterSection { get; set; }

    /* This code performs the logic needed to check for optional fields
    and to find the number of times that certain fields are repeated */
    public PriceHistoryResponse(byte[] responseBytes) : base(responseBytes)
    {
        countData = new CountData();
        ParseResponseSection(countData);

        quotes = new List<Quote>();
        for (int i = 0; i < countData.quoteCount; i++)
        {
            quotes.Add(new Quote());

            quotes[i].symbolData = new SymbolData();
            ParseResponseSection(quotes[i].symbolData);

            if (quotes[i].symbolData.errorCode == 1)
            {
                quotes[i].errorData = new ErrorData();
                ParseResponseSection(quotes[i].errorData);
                break;
            }

            quotes[i].chartBarData = new ChartBarData();
            ParseResponseSection(quotes[i].chartBarData);

            quotes[i].chartBars = new List<ChartBar>();
            for (int j = 0; j < quotes[i].chartBarData.chartBarCount; j++)
            {
                quotes[i].chartBars.Add(new ChartBar());
                ParseResponseSection(quotes[i].chartBars[j]);
            }
        }

        endingDelimiterSection = new EndingDelimiterSection();
        ParseResponseSection(endingDelimiterSection);
    }
}

class CountData : IResponseSection
{
    public int quoteCount { get; set; }
}

public class Quote
{
    public SymbolData symbolData { get; set; }
    public ErrorData errorData { get; set; }
    public ChartBarData chartBarData { get; set; }
    public List<ChartBar> chartBars { get; set; }
}

public class SymbolData : IResponseSection
{
   public string symbol { get; set; }
   public byte errorCode { get; set; }
}

public class ErrorData : IResponseSection
{
    public string errorText { get; set; }
}

public class ChartBarData : IResponseSection
{
    public int chartBarCount { get; set; }
}

public class ChartBar : IResponseSection
{
    public float close { get; set; }
    public float high { get; set; }
    public float low { get; set; }
    public float open { get; set; }
    public float volume { get; set; }
    public long timestamp { get; set; }
}

Ответы [ 3 ]

1 голос
/ 31 августа 2011

Существует много проектов, которые позволят вам (де) сериализовать классы, но если формат установлен и он не соответствует каким-то стандартным, например, буферам протоколов, то, я думаю, они вам мало помогут.Вы уверены, что это не какой-то стандартный формат?

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

Если вы хотите избежать необходимости вручную кодировать эти «парсеры», вы можете создать небольшой DSL или что-то, что сгенерирует классы данных и сопровождающий код для чтения избайтовый массив.Но это похоже на то, чтобы проделать большую работу, чтобы немного сэкономить.

Сколько разных классов вам нужно, чтобы иметь возможность читать из этих байтовых массивов?Моим первым предположением было бы просто написать код вручную.Вам понадобится много разных классов или сложной логики, чтобы оживить ваши усилия по разработке.

Еще несколько случайных идей: посмотрите на исходный код реализаций буфера протокола, возможно, вы сможете выбрать идеи из них.http://code.google.com/p/protobuf-net/ или http://code.google.com/p/protobuf-csharp-port/

Ргдс Герт-Ян

1 голос
/ 31 августа 2011

Я вставил ваш код в VS, несколько раз щелкнул «Сгенерировать заглушку метода» и переместил его. Я думаю, это бы сработало.

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

 public class BinaryResponse {

        private BinaryReader _rdr;
        public BinaryResponse(byte[] responseBytes) {
            _rdr = new BinaryReader(new MemoryStream(responseBytes)); // wrap the byte[] in a BinaryReader to be able to pop the bytes off the top
        }

        protected void ParseResponseSection(CountData countData) {
            countData.quoteCount = _rdr.ReadInt16(); // guessing 64.000 quotes should be enough in one response, the documentation will have the type      
        }

        protected void ParseResponseSection(SymbolData symbolData) {
            symbolData.errorCode = _rdr.ReadByte(); // depending on your format, where is the ErrorCOde in the byte[]? the symbol might be first

            int symbolLength = _rdr.ReadInt16(); // if it's not written by a .Net WriteString on the other end better to read this count yourelf
            symbolData.symbol = new string(_rdr.ReadChars(symbolLength)); // read the chars and put into string
        }

        protected void ParseResponseSection(ErrorData errorData) {
            int errorLenth = _rdr.ReadInt16();
            errorData.errorText = new string(_rdr.ReadChars(errorLenth));
        }

        protected void ParseResponseSection(ChartBarData chartBarData) {
            chartBarData.chartBarCount = _rdr.ReadInt16();
        }

        protected void ParseResponseSection(ChartBar chartBar) {
            // check the order with the documentation, also maybe some casting is needed because other types are in the byte[]
            chartBar.close = _rdr.ReadSingle();
            chartBar.high = _rdr.ReadSingle();
            chartBar.low = _rdr.ReadSingle();
            chartBar.open = _rdr.ReadSingle();
            chartBar.timestamp = _rdr.ReadInt64();
        }

        protected void ParseResponseSection(EndingDelimiterSection endingDelimiterSection) {
            int checkValue = _rdr.ReadInt16();
            if (checkValue != 12345) throw new InvalidDataException("Corrupt Response! Expecting End Delimiter"); // assert that the end delimiter is some value
        }
    }

Это то, что вы ищете? Вы ничего не сказали о кодировке, возможно, вам придется это учитывать при чтении байтов и т. Д.

С уважением, Герт-Ян

1 голос
/ 31 августа 2011

Вы можете попробовать сделать что-то вроде этого:

public object ByteArrayToObject(byte[] byteArray)
{
    try
    {
        // convert byte array to memory stream
        System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(byteArray);

        // create new BinaryFormatter
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binaryFormatter
                        = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

        // set memory stream position to starting point
        memoryStream.Position = 0;

        // Deserializes a stream into an object graph and return as a object.
        return binaryFormatter.Deserialize(memoryStream);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Exception caught in process: {0}", ex.ToString());
    }

    return null;
}

EDIT

Взгляните на protobuf-сеть. Я думаю, что это то, что вам нужно:

http://code.google.com/p/protobuf-net/

protected bool EvaluateBuffer(byte[] buffer, int length)
{
    if (length < 8)
    {
        return false;
    }

    MessageType messageType = (MessageType)BitConverter.ToInt32(buffer, 0);
    int size = BitConverter.ToInt32(buffer, 4);
    if (length < size + 8)
    {
        return false;
    }

    using (MemoryStream memoryStream = new MemoryStream(buffer))
    {
        memoryStream.Seek(8, SeekOrigin.Begin);
        if (messageType == MessageType.MyMessage)
        {
            MyMessage message = 
                ProtoBuf.Serializer.Deserialize<MyMessage>(memoryStream);
        }
    }
}
...