5

I am overloading the System.IO BinaryReader to serialize some classes for file storage purposes. I have had no issues doing items like dictionaries and such, but have not been successful with a nullable type. Is it possible to do? Specifically I am attempting decimal? and string?, but any types should work for me to adapt my solution to.

I have to do binary serialization for specific business reasons, so please limit responses to only solutions that work for that.

For Example... for Reading/Writing a Byte Array I use these methods:

    public byte[] ReadByteArray()
    {
        int len = ReadInt32();
        if (len > 0) return ReadBytes(len);
        if (len < 0) return null;
        return new byte[0];
    }

    public override void Write(byte[] b)
    {
        int len = b.Length;
        Write(len);
        if (len > 0) base.Write(b);
    }
RiddlerDev
  • 7,370
  • 5
  • 46
  • 62

2 Answers2

6

You will need to add some kind of flag to let the reader know if it should read the next bytes or not.

public decimal? ReadNullableDecimal()
{
    bool hasValue = ReadBoolean();
    if (hasValue) return ReadDecimal();
    return null;
}

public void Write(decimal? val)
{
    bool hasValue = val.HasValue;
    Write(hasValue)
    if(hasValue)
        Write(val.Value);
}

However we can be clever and create a generic method that works for all struct based types

public Nullable<T> ReadNullable<T>(Func<T> ReadDelegate) where T : struct
{
    bool hasValue = ReadBoolean();
    if (hasValue) return ReadDelegate();
    return null;
}

public void Write<T>(Nullable<T> val) where T : struct
{
    bool hasValue = val.HasValue;
    Write(hasValue)
    if(hasValue)
        Write(val.Value);
}

If I wanted to use my ReadNullable function to read a Int32 I would call it like

Int32? nullableInt = customBinaryReader.ReadNullable(customBinaryReader.ReadInt32);

So it would test if the value exists, then if it does it would then call the passed in function.


EDIT: After sleeping on it, the Write<T> method may not work like you expect it to. Because T is not a well defined type the only method that could support it would be Write(object) which Binary writer does not support out of the box. ReadNullable<T> will still work, and if you want to use still use Write<T> you will need to make the result of val.Value dynamic. You will need to benchmark to see if there are any performance issues with this.

public void Write<T>(Nullable<T> val) where T : struct
{
    bool hasValue = val.HasValue;
    Write(hasValue)
    if(hasValue)
        Write((dynamic)val.Value);
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • In case the value is null, write a 0 in lieu of the value. When reading, if the value is null, just read the zero and throw it away. That way, you do not need a flag. – Tarik Jul 09 '13 at 05:13
  • Umm @Tarik, So how do you tell the diffrence between `decimal? foo = 0` and `decimal? foo = null`? – Scott Chamberlain Jul 09 '13 at 05:14
  • Read hasValue boolean. Case True, ReadDecimal and return it. Case hasValue false, ReadDecimal and return null. – Tarik Jul 09 '13 at 05:27
  • @Tarik I don't understand how that is "not needing the flag", your first step is *"Read hasValue boolean"*. I could see calling `Write(default(T))` instead of writing nothing at all, but that is just a waist of space and extra I/O that is unnecessary that would slow the reader down. – Scott Chamberlain Jul 09 '13 at 05:31
-1
public decimal ReadDecimal()
{
    int len = ReadInt32();
    if (len > 0) return base.ReadDecimal();
    if (len < 0) return null;
    return new decimal;
}


public override void WriteDecimal(decimal d)
{
    if (d==null)
        WriteInt32(-1);
    else
    {
        WriteInt32(sizeof(d)); //16
        Write(d);
    }
}
user1016736
  • 390
  • 4
  • 7
  • Surely WriteDecimal(decimal d) is more explicit (for code readability) and symmetrical with ReadDecimal(). Also, 'WriteDecimal' is distinct from the superclass 'Write' so avoids the "base" specification. – user1016736 Jul 09 '13 at 01:46
  • Why waste four bytes on the Int32 length that you don't even use when a simple single-byte bool is all that's required to determine if a value should be read or not? If you're trying to save space by not writing a non-null value of 0, then you could use a single byte for that, too: 0 to indicate a non-null 0, 1 to indicate a value is stored and can be read, and 2 to indicate null. Or if you really like the negative to indicate null, use an sbyte instead... – dynamichael May 28 '20 at 03:12