0
\$\begingroup\$
public abstract class Element
{
    // Removed for verbosity ...

    protected abstract byte[] GetBytes();
}

I'm attempting to create an engine that can create a document in a very specific format. The document itself is composed of various elements, and each element has a very specific binary representation.

I originally thought that perhaps I should have an abstract method that returns a byte[] of the byte data will be written to a stream (file, memory, whatever). For example, one element would be formatted as so:

public sealed class ElementX : Element
{
    // Removed for verbosity ...

    public override byte[] GetBytes()
    {
        var nl      = Encoding.GetBytes("\r\n");
        var opening = Encoding.GetBytes("begin-elem-x");
        var closing = Encoding.GetBytes("end-elem-x");

        var data   = Encoding.GetBytes(Data);
        var result = Encoding.GetBytes(Result);

        return Util.BlockCopy(new[] { opening, nl, data, nl, closing, nl, result });
    }
}

The problem is that it just doesn't feel right to have a method that is meant for serialization. I thought about creating a custom serializer, but since each element is serialized in a very specific format (too much variation from element to element), I think that each element needs to own the code that allows it to determine the binary representation of itself; I don't know if a class exclusively for serialization of this format will work, as it would simply consist of a large if-else/switch branch and it would force me to expose private members as internal/protected/public so that this serializer could properly serialize the element.

I'm also not sure if there are any interfaces that specifically deal with this that are part of the Portable Class Libraries (ISerializable is not).

Additionally, I know there is a Code Analysis rule stating not to return a byte[] as a public property getter (CA1819: Properties should not return arrays). I don't think this necessarily applies to methods though.

I was thinking about using ImmutableArray from the Immutable Collections NuGet instead of byte[]. This seems to make sense because the value returned is immutable at the time; the value returned will only be different if a property value on the class is changed.

\$\endgroup\$

closed as off-topic by RubberDuck, Marc-Andre, mdfst13, Malachi, syb0rg May 27 '16 at 14:59

This question appears to be off-topic. The users who voted to close gave this specific reason:

If this question can be reworded to fit the rules in the help center, please edit the question.

  • 2
    \$\begingroup\$ Could you clarify your intent, I mean often you can do ToArray. Why do you need a method? \$\endgroup\$ – Greg May 25 '16 at 21:11
2
\$\begingroup\$

I could be wrong, but I haven't seen other designs of serialization than :

  1. Serializer with reflection (customizable through attributes/converter/mapper).

    This design is more complicated, in a sense that you have to plan ahead every the possible details of serialization. It is also harder to wire everything up, while leaving room for customization.

    it would simply consist of a large if-else/switch branch

    This really shouldn't happen unless you plan to do this : switch(element.GetType().Name). You should use reflection to pull every member or property(pick one), and use Attribute to filter amd format them. The only switch you might need to have is opposibly for the premitive types : string, int, enum, etc.

    it would force me to expose private members as internal/protected/public

    You don't have to, as you can obtain information on any member of a class, be protected or private.

  2. Serializable by exposing a serialize method

    This design is more straight-forward where each element serializes its own stuffs and delegates the rest to the respective element.


I'm going to assume the document is composed of hierarchical elements. If that is case, copying and merging the bytes array could get very costly, more as we get closer to the root element. Since you mentionned that you're working with a "very specific binary representation" and writing to a "stream", it would be best to write directly on the stream, rather than copying and merging the bytes, and holding them around.

public abstract class Element
{
    // Removed for verbosity ...

    protected abstract void Serialize(BinaryWriter writer);
}

public class ElementX : Element
{
    public string Result { get; set; }
    public Element Data { get; set; }

    protected override void Serialize(BinaryWriter writer)
    {
        const string NewLine = "\r\n";
        const string Opening = "begin-elem-x";
        const string Closing = "end-elem-x";

        writer.Write(Opening);
        writer.Write(NewLine);
        writer.Write(Data);
        writer.Write(NewLine);
        writer.Write(Closing);
        writer.Write(Result);
    }
}

internal static class SerializationExtensions
{
    public static void Write(this BinaryWriter writer, Element element)
    {
        element.Serialize(writer);
    }
}
\$\endgroup\$
1
\$\begingroup\$

I would define IElementWriter and IElementReader using your content terminology. Let’s say for writer:

interface IElementWriter : IDisposable
{
    void Begin<TElement>();
    void End<TElement>();
    void NewLine();

    // Probably you can have a fine grained API 
    // here for better logging - something specific for your 
    // document content and potentially reusable between elements.
    // It will insure consistency on serialization formats.
    // I use CommandDiscriminator, Angle, Speed, DepthIncrement,
    // Version, Time, ... in my binary protocol - just to get an idea.
    void Data(string value); 
    void Result(string value);
}

We implement it once for stream writing:

class StreamElementWriter : IElementWriter
{
    public StreamElementWriter(Stream stream)
        : this(new StreamWriter(stream))
    {
    }
}

Another one for tracing purposes – here fine grained API is very useful.

class LogElementWriter : IElementWriter
{
    public LogElementWriter(TextWriter writer)
    {
    }
}

Composite pattern:

class CompositeElementReader : IElementWriter
{
    public CompositeElementReader(params IElementWriter[] writers)
    {
        Writers = writers;
    }

    void Begin<TElement>()
    {
        foreach (var writer in Writers)
            writer.Begin<TElement>();
    }

    IElementWriter[] Writers { get; }
}

Now you could have:

IElementReader reader = ..
var element = Element.Load(reader);

and:

IElementWriter writer = ...
element.WriteTo(writer);

ToArray method might be useful if you do need BLOB:

class Element
{
    public byte[] ToArray()
    {
        using (var stream = new MemoryStream())
        using (var writer = new StreamElementWriter(stream))
        {
            WriteTo(stream);
            return stream.ToArray();
        }
    }
}
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.