views:

109

answers:

4

I'm writing some classes that are going to be used to communicate with a C# TCP server, and I'm writing serialization/deserialization methods using BinaryWriter and BinaryReader. This is easy enough, but I've run into a situation where I have a class Class1 that contains an instance Class2 inside. Both classes implement an interface that defines methods byte[] ToBytes() and void FromBytes(byte[] data). So I've got this code:

public class Class1
{
    public int Action;
    public Class2 Data;

    public void FromBytes(byte[] data)
    {
        BinaryReader br = new BinaryReader(new MemoryStream(data));
        Action= br.ReadInt32();
        Data = new Class2();
        Data.FromBytes(br.ReadBytes(___));
        br.Close();
    }
}

The problem is, what goes into the ReadBytes call? I'm not sure of a way to do it other than to hard-code the size of any object that implements this interface, but is there something more elegant? I considered using reflection to calculate it at run-time (caching it for future calls), but this seems even uglier.

edit: I should point out that if the data is going to be variable-length, the binary representation includes the data lengths where necessary, and the deserializer would handle it.

+2  A: 

Perhaps put the size of the instance at the beginning of the instance's data? Although, if you're using a BinaryReader, you should be able to just pass the reader itself to a method in the Class2 object and it can use it to deserialize the binary data.

nasufara
+1  A: 

If you are sure that every object of Class2 delivers the same amount of bytes when using ToBytes, create a dummy object of Class2 and use ToBytes().Length.

Or just:

    Data = new Class2();
    Data.FromBytes(br.ReadBytes(Data.ToBytes().Length));

(obviously you can make this more efficient by storing Data.ToBytes().Length somewhere and reuse it).

Doc Brown
good idea, it's obvious but somehow didn't occur to me.
toasteroven
Let's hope there are never any strings (or other data of unpredictable length) in the data ;-p
Marc Gravell
there actually aren't, it's all numeric, but a fair warning. maybe I should just go with protocol buffers like everyone else ;)
toasteroven
+2  A: 

Typically, rather than passing a byte[], I would pass the Stream (perhaps a constrained-stream, to stop it from reading outside of our expected region) or the wrapping reader, and use that - i.e. let the class read what it needs.

Another simple option is to length-prefix the data during serialization, so that you know the size is in advance.

Or, save a lot of bother and use a pre-rolled binary serialization framework. There are several... they have all had to solve this problem. Happy to advise (it is a pet area of mine).

I also worry about separation of concerns in your class, but that is a side-issue.

Marc Gravell
I'm familiar with protobuf-net, but that would be a bigger commitment than I want at the moment (maybe, hopefully down the line). are there any pure .net solutions you're familiar with and would recommend?
toasteroven
By "pure .net" do you mean "in the MS BCL"? probably not for your scenario. protobuf-net is pure .net, though. And note I didn't even mention it! I had to bite my tongue hard, though.
Marc Gravell
+1  A: 

If I understood your problem correctly, and without going into the merits of how correct your approach to this problem is, Marshal.SizeOf should solve your problem.

I took the liberty of filling the blanks in your code:


class Program
{
    [StructLayout(LayoutKind.Sequential)]
    public class Class2
    {
        public int Data;
        public int SomeMoreData;

        internal void FromBytes(byte[] p)
        {
            BinaryReader br = new BinaryReader(new MemoryStream(p));
            Data = br.ReadInt32();
            SomeMoreData = br.ReadInt32();
            br.Close();
        }
    }

    public class Class1
    {
        public int Action;
        public Class2 Data;

        public void FromBytes(byte[] data)
        {
            BinaryReader br = new BinaryReader(new MemoryStream(data));
            Action = br.ReadInt32();
            Data = new Class2();
            int sizeOfClass2Instance = Marshal.SizeOf(Data);
            Data.FromBytes(br.ReadBytes(sizeOfClass2Instance));
            br.Close();
        }
    }

    static void Main(string[] args)
    {
        Class1 c1 = new Class1();

        int[] data = new int[3] { 1, 2, 3 };
        byte[] dataInBytes = new byte[data.Length * sizeof(int)];

        for (int i=0;i<data.Length;i++)
        {
            byte[] src = BitConverter.GetBytes(data[i]);
            Array.Copy(src, 0, dataInBytes, i * sizeof(int), sizeof(int));
        }
        c1.FromBytes(dataInBytes);
    }
}


The trick is, Marshal.SizeOf relies on the StructLayout attribute on your class. You must be explicit on how data is stored in your class.

Padu Merloti