views:

435

answers:

3

This is what I've come up with so far, but it doesn't seem very optimal, any ideas on better approaches?

public void ToBytes(object[] data, byte[] buffer)
{
    byte[] obytes;
    int offset = 0;

    foreach (object obj in data)
    {
        if (obj is string)
            obytes = System.Text.Encoding.UTF8.GetBytes(((string)obj));
        else if (obj is bool)
            obytes = BitConverter.GetBytes((bool)obj);
        else if (obj is char)
            obytes = BitConverter.GetBytes((char)obj);
        // And so on for each valuetype

        Buffer.BlockCopy(obytes, 0, buffer, offset, obytes.Length);
        offset += obytes.Length;
    }
}
+1  A: 

Probably, you should consider using BinaryFormatter instead:

var formatter = new BinaryFormatter();
var stream = new MemoryStream();
formatter.Serialize(stream, obj);
byte[] result = stream.ToArray();

Beside that, there are some pretty good serialization frameworks like Google Protocol Buffers if you want to avoid reinventing the wheel.

Mehrdad Afshari
Thanks. I would have used BinaryFormatter if there was a way to deserialize the data in Java as well. I'll look into the google protocol buffers. Do you know if there's a Java version?
remdao
remdao: Yes, there's a Java version of it: http://code.google.com/p/protobuf/ If you want to have a cross platform serialization code, there's another C# implementation by Jon that works better for that scenario: http://code.google.com/p/protobuf-csharp-port
Mehrdad Afshari
+4  A: 

Well, you could have a map like this:

private static readonlyDictionary<Type, Func<object, byte[]>> Converters = 
    new Dictionary<Type, Func<object, byte[]>>()
{
    { typeof(string), o => Encoding.UTF8.GetBytes((string) o) },
    { typeof(bool), o => BitConverter.GetBytes((bool) o) },
    { typeof(char), o => BitConverter.GetBytes((char) o) },
    ...
};

public static void ToBytes(object[] data, byte[] buffer)
{
    int offset = 0;

    foreach (object obj in data)
    {
        if (obj == null)
        {
            // Or do whatever you want
            throw new ArgumentException("Unable to convert null values");
        }
        Func<object, byte[]> converter;
        if (!Converters.TryGetValue(obj.GetType(), out converter))
        {
            throw new ArgumentException("No converter for " + obj.GetType());
        }

        byte[] obytes = converter(obj);
        Buffer.BlockCopy(obytes, 0, buffer, offset, obytes.Length);
        offset += obytes.Length;
    }
}

You're still specifying the converter for each type, but it's a lot more compact than the if/else form.

There are various other ways of constructing the dictionary, btw. You could do it like this:

private static readonly Dictionary<Type, Func<object, byte[]>> Converters = 
        new Dictionary<Type, Func<object, byte[]>>();

static WhateverYourTypeIsCalled()
{
    AddConverter<string>(Encoding.UTF8.GetBytes);
    AddConverter<bool>(BitConverter.GetBytes);
    AddConverter<char>(BitConverter.GetBytes);
}

static void AddConverter<T>(Func<T, byte[]> converter)
{
    Converters.Add(typeof(T), x => converter((T) x));
}


I see another answer has suggested binary serialization. I'm personally not keen on "opaque" serialization schemes like that. I like to know exactly what's going to be in the data in a way that means I can port it to other platforms.

I would point out, however, that your current scheme doesn't give any sort of delimiter - if you have two strings, you'd have no idea where one stopped and the other started, for example. You also don't store the type information - that may be okay, but it may not be. The variable length issue is usually more important. You might consider using a length-prefix scheme, like the one in BinaryWriter. Indeed, BinaryWriter may well be a simpler solution in general. You'd probably want to still have a map of delegates, but make them actions taking a BinaryWriter and a value. You could then build the map by reflection, or just a hardcoded list of calls.

Then you'd just initialize a BinaryWriter wrapping a MemoryStream, write each value to it appropriately, then call ToArray on the MemoryStream to get the results.

Jon Skeet
More importantly, the current scheme doesn't provide any kind of type information. You'll have no idea whether what you are reading back is an `integer` or a `float`, for instance.
Mehrdad Afshari
@Mehrdad: That may be a problem, but it may not. The type information *may* be implicit - e.g. the objects could be the values of fields in a type, where the overall type is known beforehand.
Jon Skeet
Thanks for you answers. I know about the variable length problem, but tried to limit the question. The solution I've been using for that is to store the length of each object as shorts in a separate byte array which i after the conversion copied to the buffer. The type information is known both by the client and the server so that is not a problem. Your solution looks good because the server needs to be able to communicati with both a .NET client and a Java client.
remdao
A: 

You can use a StreamWriter to write to a memory stream and use its buffer:

  {
               byte[] result;
            using (MemoryStream stream = new MemoryStream())
            {
                StreamWriter writer = new StreamWriter(stream);
                writer.WriteLine("test");
                writer.WriteLine(12);
                writer.WriteLine(true);

                writer.Flush();

                result = stream.GetBuffer();
            }

            using(MemoryStream stream=new MemoryStream(result))
            {
                StreamReader reader = new StreamReader(stream);
               while(! reader.EndOfStream)
                 Console.WriteLine(reader.ReadLine());
               }
            }
Beatles1692