使用C#,如何将二进制数据的字节数组转换为对数据建模的自定义类型对象?

时间:2011-08-31 13:44:58

标签: c# .net serialization reflection binary

场景:我通过HTTP接收了原始二进制数据,并将数据存储到字节数组中。我有描述二进制数据可以表示的各种字段的文档,但必须在运行时确定数据的实际含义。例如,如果表示错误发生的字节= 1,则下一个字节的含义会发生变化。

在.NET 4中使用C#,我想创建一个或多个镜像文档中描述的字段的类,然后以某种方式使用二进制数据的字节数组初始化类。我希望该解决方案能够最大限度地减少代码重复,并且模块化和优雅。

我已经开始研究创建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 个答案:

答案 0 :(得分:1)

您可以尝试这样做:

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;
}

修改

看看protobuf-net。我认为这就是你所需要的:

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);
        }
    }
}

答案 1 :(得分:1)

有很多项目可以让你(de)序列化类,但是如果设置了格式并且它不像协议缓冲区这样的标准,那么我想它们对你没什么帮助。你确定它不是一些标准格式吗?

如果你需要读取一个字节来找出下一个字节的含义,我猜你需要使用BinaryReader逐字节读取它们,然后将字节数组的其余部分传递给另一个函数根据结果​​。

如果你想避免手工编码这些'解析器'那么你可以制作一个小的DSL或者生成数据类和附带代码的东西来从字节数组中读取一个。但这听起来像做了很多工作来节省一点。

您需要从这些字节数组中读取多少个不同的类?我的第一个猜测就是手工编写代码。您需要有许多不同的类或复杂的逻辑来恢复您的开发工作。

一些随机的想法:查看协议缓冲区实现的源代码,也许你可以从中选择想法。 http://code.google.com/p/protobuf-net/http://code.google.com/p/protobuf-csharp-port/

Rgds Gert-Jan

答案 2 :(得分:1)

我将你的代码粘贴到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
        }
    }

这是你要找的吗?你没有说任何关于编码的内容,你可能需要在读取字节等时考虑到这一点。

关心Gert-Jan