新的“using”语法与长期引用的交互

New `using` syntax interaction with long lived references

这是一些示例代码;

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

BinaryReaderusing语句的行为是什么?

“常识”告诉我,一旦离开 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) };
    }
}

在任何一种风格中,这都是一个代码错误,持有 class 将始终包含已处置的引用,因为它在调用者有权访问由返回的 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 并在处理时适当处理它。