使用BinaryReader读写可空类型?

5
我正在重载System.IO BinaryReader以序列化一些类用于文件存储目的。我在像字典等项目上没有遇到任何问题,但是在可为空类型上并不成功。这是否可能?具体而言,我正在尝试decimal?和string?,但任何类型都应该适用于我来调整我的解决方案。
由于特定业务原因,我必须进行二进制序列化,请仅限制响应仅适用于此的解决方案。
例如...对于读/写字节数组,我使用以下方法:
    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);
    }

2
你能提供一个代码示例吗? - IAbstract
你有没有什么理由不能使用内置的BinaryFormatter? - default.kramer
内置功能存在速度问题。通过这种方式,我获得了20-100倍的速度提升。 我认为我解决了这个问题——在开头检查值是否为空,然后根据需要使用默认的读取器。 - RiddlerDev
byte[] myNull = ReadByteArray();//使用负的int32 'len' - user1016736
2个回答

6

您需要添加某种标志来让读者知道是否应该读取下一个字节。

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);
}

然而,我们可以聪明地创建一个通用的方法,适用于所有基于结构的类型。
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);
}

如果我想使用我的ReadNullable函数读取一个Int32,我可以这样调用它:
Int32? nullableInt = customBinaryReader.ReadNullable(customBinaryReader.ReadInt32);

所以它会测试值是否存在,如果存在,则调用传入的函数。
编辑:经过一晚上的思考,Write<T>方法可能不会像您期望的那样工作。因为T不是一个明确定义的类型,唯一支持它的方法将是Write(object),但二进制写入器不原生支持它。 ReadNullable<T>仍将起作用,如果您想使用Write<T>,则需要使val.Value的结果动态。 您需要进行基准测试,以查看是否存在任何性能问题。
public void Write<T>(Nullable<T> val) where T : struct
{
    bool hasValue = val.HasValue;
    Write(hasValue)
    if(hasValue)
        Write((dynamic)val.Value);
}

如果值为null,则写入0代替该值。在读取时,如果值为null,则只需读取零并将其丢弃。这样,您就不需要一个标志。 - Tarik
嗯@Tarik,那么你如何区分 decimal? foo = 0decimal? foo = null 的差别呢? - Scott Chamberlain
读取 hasValue 布尔值。如果为 True,则读取十进制数并返回它。如果为 false,则读取十进制数并返回 null。 - Tarik
@Tarik 我不明白这是如何"不需要标志"的,你的第一步是*"读取hasValue布尔值"*。我可以理解调用Write(default(T))而不是什么都不写,但那只是浪费空间和额外的I/O,是没有必要的,会减慢读取速度。 - Scott Chamberlain

-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);
    }
}

毫无疑问,对于代码可读性来说,使用WriteDecimal(decimal d)更加明确和对称化,并且与ReadDecimal()相呼应。此外,“WriteDecimal”与超类“Write”不同,因此避免了“base”规范的使用。 - user1016736
为什么要浪费四个字节的Int32长度,而你根本不用它,当一个简单的单字节bool就足以确定是否应该读取值?如果你试图通过不写一个非空0来节省空间,那么你也可以使用一个字节:0表示非空0,1表示存储了一个可以被读取的值,2表示null。或者,如果你真的喜欢使用负数表示null,那么可以使用sbyte。 - dynamichael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接