2016-05-23 139 views
1

我有可以使用不同的串行(的BinaryFormatterXmlSerializer的Json.Net)将数据写入到文件系统时,如何正确处理处置流和位置。我已将它们包装在我自己的IStreamSerializer接口中,并希望确保它们在我的应用程序环境中表现相同。这是我的一种测试方法:序列化和反序列化

[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

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

我知道处置然而,Json.Net是,当我重置流位置为零抛出异常在的末尾使用声明调用,这就是我返回到测试类方法时无法设置流位置的原因。

我该如何使这项工作正确?我已经尝试了很多可能的解决方案,但他们要么最终破坏序列化的文件或抛出一个错误,如无法读取流

这将引发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只是如果我试图用同样的方式已经不写正确的数据。

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

+3

你需要处置'StreamWriter' ** AFTER **你完成了使用'JSONTextWriter'并处置它。此外,文件被“破坏”是因为你没有处理这些流。 dispose方法通常也会将剩余内存刷新到文件中 – Sidewinder94

+2

StreamWriter将取得所有权并关闭Dispose上的流。 'bool leaveOpen'有一个构造函数重载。在这里寻找更多的信息:https://msdn.microsoft.com/en-us/library/gg712853(v=vs.110).aspx –

+0

使用语句已经这样做,对吧?但只要一切都结束了,我不能再做stream.Position = 0了,那就是问题所在。但我也需要关闭Json.Net才能正常工作。也许这是他们的序列化程序中的一个错误。 – Xarbrough

回答

3

StreamWriter默认取得您传入的流的所有权,因此当您处理流式编写器时,它会处理您传入的流,如果您使用this constructor,则可以传入一个布尔值,告诉它不要处理即传入的流。

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

你只需要以旧的构造函数已通过在第二和第三参数,这将是一个UTF8Encoding没有字节顺序标记和1024分别默认值来传递。

*我使用命名参数,因为我不喜欢传入神秘常量,使用命名参数,它使它更明显是什么1024true表示。


作为替代的解决方案,如果你不上.NET 4.5或更新的象下面穿过所有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); 
    } 
} 
+0

如果使用.Net 4.6,这是一个很好的答案。我个人不能使用它,因为我的框架只支持.Net 3.5。 – Xarbrough

+0

构造函数是4.5而不是4.6,但仍然不能帮助你。 –

+0

@Xarbrough查看我更新的pre.net 4.5选项的答案。你可能需要修剪一两个方法。我在.NET 4.5中创建了这个类,所以就像'FlushAsync('在那里,需要删除。 –

0

基础上的意见,我想出了这个解决方案:

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

我发现Json.Net可直接与的StreamWriter工作。所以现在我最后冲洗它,但保持打开状态。就我的单元测试和一些实际测试而言,这是有效的。

这是一个有效的解决方案还是我绝对必须处置StreamWriter?这些内存是否泄漏问题或安全忽略?

+0

处理流写入器并不是什么大问题 - 尽管正确配置stream本身是必要的,所以请确保调用者根据需要处理流。 – Luaan

+0

@Luaan如果流是一个'MemoryStream',如果它没有被丢弃,它也不是什么大事。所有处理内存流的操作都会设置一些标志,以防止在完成后写入一些内容,并将一些内容设置为null,以便可以尽早对其进行GC处理。这就是说,如果我曾经写过'Stream'(包括'MemoryStream')而不处理它,我认为我需要洗个澡才能洗掉我感觉不到的东西:) –

+0

@ScottChamberlain嗯,我有点假定内存流只在测试中使用,但当然,处置内存流的意义不大 - 实际上它甚至不会将缓冲区设置为空。 – Luaan