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

标签 c# .net serialization reflection binary

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

将 C# 与 .NET 4 结合使用,我想创建一个或多个类来反射(reflect)文档中描述的字段,然后使用二进制数据的字节数组以某种方式初始化这些类。我希望解决方案能够最大程度地减少代码重复,并且模块化且优雅。

我已经研究过创建可序列化的类,但我不知道它是如何工作的,因为我从一个不是我创建的(因此也不是序列化的)字节数组开始。

我还尝试使用泛型和反射来检索自定义类中包含的字段的大小和类型。然后我使用该信息从字节数组中动态切出数据并将其分配给相应的字段。然而,这种方法导致了很多难看的、难以管理的代码。

任何有关为该问题设计可扩展、解耦的解决方案的建议或指示都将不胜感激。

编辑:包含反射(reflect)规范中字段的字段的类示例

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

最佳答案

我将您的代码粘贴到 VS 中,点击了几次“生成方法 stub ”并将其移动了一些。我想这可以解决问题。

您提供的代码非常聪明,它有点像访问者模式,其中重载切换到正确的方法。

 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

关于c# - 使用 C#,如何将二进制数据的字节数组转换为对数据建模的自定义类型对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7257838/

相关文章:

c# - LINQ 可以在集合排序时使用二分查找吗?

c# - 如何获取与 .NET 属性关联的类/属性/等?

c# - 我在我的目录中找不到 .sln 文件,那么如何在 Visual Studio 中打开我的项目?

java - 如何使用 Jackson API 对序列化和反序列化使用不同的 JSONProperty?

symfony - 在 Symfony2 中运行 PHPUnit 测试时,JMS Serializer 不读取配置

c# - EF4.1 - Fluent API - SqlQuery - 调用存储过程时的配置映射 - 数据读取器与指定的实体类型不兼容

c# - 使用程序集链接器将资源文件链接到现有的 .NET 程序集

.net - 是否有具有相同公共(public)接口(interface)的 System.Drawing.Graphics 的直接替代品?

jquery - .NET 相当于 JQuery.param()

android - 将多个参数放入 ContentResolver.requestSync