序列化和反序列化时如何正确处理流处理和定位
How to correctly handle stream dispose and position when serializing and deserializing
我有一个可以使用不同序列化程序的系统(BinaryFormatter、XmlSerializer、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,将其序列化并尝试读回,断言 returned 对象是否相同。这适用于 BinaryFormatter 和 XmlSerializer。但是,当我将流位置重置为零时,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);
}
}
我知道 Dispose 在 using 语句的末尾被调用,这就是我无法设置流位置的原因当我 return 回到我的测试 class 方法时。
我怎样才能使它正常工作?我已经尝试了很多可能的解决方案,但它们最终要么破坏了序列化文件,要么抛出类似 无法读取流。
这会抛出 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);
}
同样,BinaryFormatter 和 XmlSerializer 在我的测试用例中都可以正常工作。当我调用 formatter.Serialize 时,它们似乎没有处理流,但是 Json.Net 只是不再写入正确的数据如果我以同样的方式尝试。
注意:我的框架只能使用自定义版本的 .Net,类似于 v3.5。
根据评论,我找到了这个解决方案:
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?这些类型的内存泄漏是一个问题还是可以安全地忽略?
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
。
* 我使用命名参数是因为我不喜欢传递神秘常量,使用命名参数可以使 1024
和 true
代表的内容更加明显。
作为替代解决方案,如果您使用的不是 .NET 4.5 或更新版本,您可以使用如下所示的 class 来传递除 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);
}
}
我有一个可以使用不同序列化程序的系统(BinaryFormatter、XmlSerializer、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,将其序列化并尝试读回,断言 returned 对象是否相同。这适用于 BinaryFormatter 和 XmlSerializer。但是,当我将流位置重置为零时,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);
}
}
我知道 Dispose 在 using 语句的末尾被调用,这就是我无法设置流位置的原因当我 return 回到我的测试 class 方法时。
我怎样才能使它正常工作?我已经尝试了很多可能的解决方案,但它们最终要么破坏了序列化文件,要么抛出类似 无法读取流。
这会抛出 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);
}
同样,BinaryFormatter 和 XmlSerializer 在我的测试用例中都可以正常工作。当我调用 formatter.Serialize 时,它们似乎没有处理流,但是 Json.Net 只是不再写入正确的数据如果我以同样的方式尝试。
注意:我的框架只能使用自定义版本的 .Net,类似于 v3.5。
根据评论,我找到了这个解决方案:
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?这些类型的内存泄漏是一个问题还是可以安全地忽略?
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
。
* 我使用命名参数是因为我不喜欢传递神秘常量,使用命名参数可以使 1024
和 true
代表的内容更加明显。
作为替代解决方案,如果您使用的不是 .NET 4.5 或更新版本,您可以使用如下所示的 class 来传递除 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);
}
}