c# - 序列化和反序列化时如何正确处理流处置和定位

标签 c# serialization dispose memorystream

我有一个可以使用不同序列化程序(BinaryFormatterXmlSerializerJson.Net)将数据写入文件的系统。我已将它们包装在我自己的 IStreamSerializer 接口(interface)中,并希望确保它们在我的应用程序上下文中的行为相同。这是我的测试方法之一:

[Test]
public void JsonSerializer_RoundtripsMap_Successfully()
{
    Map map = new Map(2, 4, TileType.Grass);
    IStreamSerializer serializer = new JsonSerializer(); // Json.Net

    using (var ms = new MemoryStream())
    {
        serializer.Serialize(ms, map);
        ms.Position = 0;
        Map loaded = serializer.Deserialize<Map>(ms);
        // Asserts...
    }
}

我创建一个MemoryStream,将其序列化并尝试读回它,断言返回的对象是否相同。这对于 BinaryFormatterXmlSerializer 可以正常工作。但是,当我将流位置重置为零时,Json.Net 抛出异常:

System.ObjectDisposedException : The object was used after being disposed.

这是我的 JsonSerializer.Serialize 方法:

public void Serialize(Stream stream, object data)
{
    var serializer = new Newtonsoft.Json.JsonSerializer();  
    using (var textWriter = new StreamWriter(stream))
    using (var jsonWriter = new JsonTextWriter(textWriter))
    {
        serializer.Serialize(jsonWriter, data);
    }
}

我知道 Disposeusing 语句末尾被调用,这就是我返回测试类时无法设置流位置的原因方法。

如何才能使其正常工作?我已经尝试了很多可能的解决方案,但它们要么最终损坏了序列化文件,要么抛出诸如无法读取流.

这会抛出JsonReaderException并破坏物理写入的文件,但它确实通过了测试:

public void Serialize(Stream stream, object data)
{
    var serializer = new Newtonsoft.Json.JsonSerializer();
    var textWriter = new StreamWriter(stream);
    var jsonWriter = new JsonTextWriter(textWriter);
    serializer.Serialize(jsonWriter, data);
}

同样,BinaryFormatterXmlSerializer 在我的测试用例中都可以正常工作。当我调用 formatter.Serialize 时,他们似乎没有处理流,但是如果我以同样的方式尝试,Json.Net 就不再写入正确的数据。 p>

注意:我的框架只能使用.Net 的自定义版本,类似于 v3.5。

最佳答案

StreamWriter 默认情况下拥有您传入的流的所有权,因此当您处置流编写器时,如果您使用 this constructor,它也会处置您传入的流。您可以传入一个 bool 值,告诉它不要处理传入的流。

private static readonly UTF8Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier:false, throwOnInvalidBytes:true);

public void Serialize(Stream stream, object data)
{
    var serializer = new Newtonsoft.Json.JsonSerializer();  
    using (var textWriter = new StreamWriter(stream, UTF8NoBOM, bufferSize:1024, leaveOpen:true))
    using (var jsonWriter = new JsonTextWriter(textWriter))
    {
        serializer.Serialize(jsonWriter, data);
    }
}

您只需传入旧构造函数为第二个和第三个参数传入的默认值,该默认值将是没有字节顺序标记和 1024UTF8Encoding > 分别。

* 我使用命名参数是因为我不喜欢传递神秘常量,使用命名参数可以让 1024true 所代表的内容更加明显。


作为替代解决方案,如果您不是使用 .NET 4.5 或更高版本,您可以使用如下所示的类来传递除 Dispose 之外的所有 Stream 命令

public class DisposeBlocker : Stream
{
    private readonly Stream _source;
    private readonly bool _blockDispose;
    private readonly bool _blockClose;

    public DisposeBlocker(Stream source, bool blockDispose = true, bool blockClose = false)
    {
        if(source == null)
            throw new ArgumentNullException(nameof(source));
        _source = source;
        _blockDispose = blockDispose;
        _blockClose = blockClose;
    }

    protected override void Dispose(bool disposing)
    {
        if (!_blockDispose && disposing)
        {
            _source.Dispose();
        }
    }

    public override void Close()
    {
        if (!_blockClose)
        {
            _source.Close();
        }
    }

    public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
    {
        return _source.CopyToAsync(destination, bufferSize, cancellationToken);
    }

    public override void Flush()
    {
        _source.Flush();
    }

    public override Task FlushAsync(CancellationToken cancellationToken)
    {
        return _source.FlushAsync(cancellationToken);
    }

    protected override WaitHandle CreateWaitHandle()
    {
        //Obsolete method, Reference Source states just return the following.
        return new ManualResetEvent(false);
    }

    public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
    {
        return _source.BeginRead(buffer, offset, count, callback, state);
    }

    public override int EndRead(IAsyncResult asyncResult)
    {
        return _source.EndRead(asyncResult);
    }

    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        return _source.ReadAsync(buffer, offset, count, cancellationToken);
    }

    public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
    {
        return _source.BeginWrite(buffer, offset, count, callback, state);
    }

    public override void EndWrite(IAsyncResult asyncResult)
    {
        _source.EndWrite(asyncResult);
    }

    public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        return _source.WriteAsync(buffer, offset, count, cancellationToken);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return _source.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
         _source.SetLength(value);
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return _source.Read(buffer, offset, count);
    }

    public override int ReadByte()
    {
        return _source.ReadByte();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _source.Write(buffer, offset, count);
    }

    public override void WriteByte(byte value)
    {
        _source.WriteByte(value);
    }

    protected override void ObjectInvariant()
    {
        //Obsolete method, nothing to override.
    }

    public override bool CanRead
    {
        get { return _source.CanRead; }
    }

    public override bool CanSeek
    {
        get { return _source.CanSeek; }
    }

    public override bool CanTimeout
    {
        get { return _source.CanTimeout; }
    }

    public override bool CanWrite
    {
        get { return _source.CanWrite; }
    }

    public override long Length
    {
        get { return _source.Length; }
    }

    public override long Position
    {
        get { return _source.Position; }
        set { _source.Position = value; }
    }

    public override int ReadTimeout
    {
        get { return _source.ReadTimeout; }
        set { _source.ReadTimeout = value; }
    }

    public override int WriteTimeout
    {
        get { return _source.WriteTimeout; }
        set { _source.WriteTimeout = value; }
    }

    public override object InitializeLifetimeService()
    {
        return _source.InitializeLifetimeService();
    }

    public override ObjRef CreateObjRef(Type requestedType)
    {
        return _source.CreateObjRef(requestedType);
    }

    public override string ToString()
    {
        return _source.ToString();
    }

    public override bool Equals(object obj)
    {
        return _source.Equals(obj);
    }

    public override int GetHashCode()
    {
        return _source.GetHashCode();
    }
}

它的用法就像

public void Serialize(Stream stream, object data)
{
    var serializer = new Newtonsoft.Json.JsonSerializer();  
    using (var textWriter = new StreamWriter(new DisposeBlocker(stream)))
    using (var jsonWriter = new JsonTextWriter(textWriter))
    {
        serializer.Serialize(jsonWriter, data);
    }
}

关于c# - 序列化和反序列化时如何正确处理流处置和定位,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37392624/

相关文章:

C# 浮点表达式 : strange behavior when casting the result float to int

c# - Entity Framework Linq、Left Join 和 Group with SUM 和 Count

c# - 我可以在 Vista 上使用 C#.NET 开发 iPhone 应用程序吗?

c# - 序列化 - 从流中查看对象图

c# - 如何全局设置 System.Text.Json.JsonSerializer 的默认选项?

c# - Visual Studio 2010 在 Designer.cs 中硬编码不需要的值

c - 序列化:char 和 uint32_t 的大小

c# - SharePoint 内存泄漏

c# - 如何在对象上实现 Dispose 创建其他一次性用品

f# - 为什么使用比使用更好?