问题描述
这是一些示例代码;
public sealed class HoldingClass : Idisposable
{
private BinaryReader _binaryReaderField;
public string GetStringFromreader(int count)
{
var data = _binaryReaderField.ReadBytes(count);
// ... Process data ... //
return newString;
}
public void dispose()
{
_binaryReaderField?.Close();
_binaryReaderField?.dispose();
}
}
// ... somewhere else ... //
public sealed class EntryPoint : Idisposable
{
public static Start(string filePath)
{
using var br = new BinaryReader(File.OpenRead(filePath));
return new EntryPoint { _holdingClass = new HoldingClass(br) };
}
private HoldingClass _holdingClass;
public string GetString(int count)
{
return _holdingClass.GetStringFromreader(count);
}
public void dispose()
{
_holdingClass?.dispose();
}
}
请注意,这是一个人为的示例,用于表达我感兴趣的行为,并不代表任何类型的生产代码。
好的,所以在上面的代码中,假设应用程序以 using var ep = EntryPoint.Start("someFile.txt");
启动,然后在发生其他处理后的方法中,开发人员然后调用 ep.GetString(155);
。
BinaryReader
using
语句的行为是什么?
“常识”告诉我,只要您离开 EntryPoint.Start
方法的范围,对象就会被释放。稍后通过 HoldingClass
实例使用它的任何尝试都将导致 Objectdisposed
异常。
但是,我不太清楚新的 using
智能行为是如何工作的。是通过引用计数完成的吗?在这种情况下,BinaryReader
对象将在 HoldingClass
实例的整个生命周期中保持活动状态。
在 C++ 中,我会通过实例化一个唯一指针并将所有权转移给新对象来解决这个问题,但据我所知,C# 不是这样工作的。
解决方法
新的 using
语法实际上只是一种语法糖,用于减少需要使用大括号来显示对象应在其后处理的范围的“噪音”。没有关于它的“新”启发式方法。您发布的代码的等效旧样式是:
public static EntryPoint Start(string filePath)
{
using (using var br = new BinaryReader(File.OpenRead(filePath)))
{
return new EntryPoint { _holdingClass = new HoldingClass(br) };
}
}
在任何一种风格中,这是一个代码错误,并且持有类将始终包含一个已释放的引用,因为它在调用者有权访问由 EntryPoint
方法返回的 Start()
实例之前已被释放.
使用旧的或新的语法,您在这里尝试做的不是 using
的适当用法,您应该将其省略,例如:
public static EntryPoint Start(string filePath)
{
var br = new BinaryReader(File.OpenRead(filePath));
try
{
return new EntryPoint { _holdingClass = new HoldingClass(br) };
}
catch (Exception)
{
br.Dispose();
throw;
}
}
如果在创建结果时发生异常,则此处处理 BinaryReader
,否则您需要将其控制权传递给您的 HoldingClass
实现并使其和 EntryPoint
实现IDisposable
并在处置时适当处置。