Scenario: I have received raw binary data via HTTP and stored the data into a byte array. I have documentation that describes various fields that the binary data can represent, but the actual meaning of the data must be determined at run-time. For example, if the byte that represents the occurrence of an error = 1, then the meaning of the next byte changes.
Using C# with .NET 4, I want to create one or more classes that mirror the fields described in the documentation and then somehow initialize the classes using the byte array of binary data. I would like the solution to minimize code duplication and also to be modular and elegant.
I have looked into creating Serializable classes, but I don't see how that can work since I am starting with a byte array that was not created (and therefore, not serialized) by me.
I have also attempted to use generics and reflection in order to retrieve the sizes and types of the fields contained in custom classes. I then used that information to dynamically slice out data from the byte array and assign it to the corresponding fields. However, this method resulted in a lot of ugly, unmanageable code.
Any advice or pointers on designing an extensible, decoupled solution for this problem would be much appreciated.
Edit: Example of classes containing fields that mirror the fields in the specification
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; }
}
I pasted your code into VS, clicked 'Generate Method Stub' a few times and moved it around some. I guess this would do the trick.
The code you provide is pretty clever, it's a bit like a visitor pattern where overloading switches to the right method.
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
}
}
Is this what you're looking for? You didn't say anything about encoding, you may need to take that into account when reading bytes etc.
Regards Gert-Jan
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With