【问题标题】:How to do Sentry pattern in C#?如何在 C# 中实现哨兵模式?
【发布时间】:2014-09-24 15:11:50
【问题描述】:

在 C++ 中,我习惯于使用哨兵模式来确保在块或函数退出时正确释放获取的资源(例如,参见 here)。例如,我用它来抓取图形状态,然后我可以对该状态做任何我想做的事情,当哨兵对象(引用)超出范围时它会被放回。

现在我正在使用 C#,但同样的技巧不起作用,因为直到后来谁知道什么时候才会调用析构函数。

当对象的最后一个引用被释放时,是否有其他方法可以保证触发?还是我只需要记住调用一些 Restore() 或 Apply() 或 DoYourThing() 方法,然后再从我会使用哨兵的任何方法返回?

【问题讨论】:

  • “是否有其他方法可以保证在释放对象的最后一个引用时触发” 没有。:-(我也对此感到遗憾。.NET Dispose 模式是最好的替代品。
  • 我假设你已经看过Implementing RAII in C#(甚至可能重复)

标签: c# c++ design-patterns


【解决方案1】:

C# 具有终结器,如 C++,在对象被销毁时调用。它们的形式与 C++ 相同,即:

~ClassName()
{
}

不过,如您所知,C# 没有确定性内存管理,因此这并不是一个很好的保证。作为直接结果,您应该使用终结器来释放 C# 中的非托管资源。相反,我们使用IDisposable 接口。这会在实现者Dispose 上公开一个方法,该方法旨在在您想要释放非托管资源时调用。

public class MyDisposable : IDisposable
{
    public void Dispose()
    {
        // get rid of some expensive unmanaged resource like a connection
    }
}

我们还可以使用using 糖来允许Dispose()using 块终止时进行语义调用(无论是否正常)。

using(var disposable = new MyDisposable)
{
    // Do something with the disposable
} // Dispose is invoked here

如果你发现自己使用终结器Dispose,你可以考虑使用GC.SuppressFinalize 方法,虽然这有点超出我的范围。在 StackOverflow 上的其他地方有一个关于这个 here

的非常好的讨论

这当然可以用来执行 RAII 式的诡计,例如

using(var guard = new Guard(resource))
{

}

【讨论】:

    【解决方案2】:

    C# 为这种情况提供了using 块,它适用于实现IDisposable 的任何对象。

    例如,如果您有一个类型 class Foo : IDisposable,那么您可以这样做:

    using (new Foo(/* ... */)) {
        // Your code that depends on this resource.
    }
    

    这相当于:

    Foo x = new Foo(/* ... */);
    try {
        // Your code
    } finally {
        if (x != null) {
            ((IDisposable)x).Dispose();
        }
    }
    

    当然,除非您无权访问x。但是,如果需要,您可以通过在 using 块中创建变量来获得此类访问权限:

    using (Foo foo = new Foo(/* ... */)) {
        // Your code that uses "foo"
    }
    

    创建可以由垃圾收集器以这种方式处理的类的标准方法是:

    class Foo : IDisposable {
        protected virtual void Dispose(bool disposing) {
            // Your cleanup code goes here.
        }
    
        public void Dispose() {
            GC.SuppressFinalize(this);
            Dispose(true);
        }
    
        ~Foo() {
            Dispose(false);
        }
    }
    

    【讨论】:

    • 谢谢大家!你们都有正确的答案(我觉得自己没有想到它是个傻瓜,因为我以前在其他地方使用过“使用”)。但是@cdhowie 在这里展示的模式特别值得了解,所以我会将此标记为答案(并投票支持其他所有人分享爱)。
    【解决方案3】:

    要实现非托管资源的确定性解除分配,您可以使用 Dispose 模式。在许多情况下,如果您希望在特定范围内发生这种情况,您可以将其与 using 块结合使用。

    图形设备的文件句柄或句柄通常被视为非托管资源。这些是 .NET 框架不跟踪引用且不会自动释放的资源。

    如果它是托管资源,只是对 C# 对象/列表的引用,那么通常不需要确定性释放,垃圾收集器通常可以比你更有效地处理这些资源。 不要太担心何时解除分配托管资源。如果您不再有对它们的引用,那么它们应该是无关紧要的。当您完成某事但不知何故有一个挥之不去的引用阻止 GC 释放它时,就会出现问题。

    【讨论】:

      【解决方案4】:

      听起来您所描述的是实现 C# 的 IDisposable 接口的对象。 IDisposable 有一个 Dispose() 方法,当在您自己的对象上正确定义时,该方法用于释放资源。

      另外,C# 确实有这样定义的析构函数:

      public class Foo
      {
          public Foo()
          {
              // constructor
          }
      
          ~Foo() // destructor
      }
      

      在 using 块中包装 IDisposable 对象时,会自动调用 Dispose() 方法,另一种选择是将其放入 finally 块中(不使用时...使用)

      【讨论】:

        【解决方案5】:

        执行此操作的最简单方法是将哨兵对象包含在 using 语句中。所以像这样。

        using (sentry = new Sentry())
        {
            //do your stuff here
        }
        

        在此块之后,哨兵将被释放并超出范围。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-06-05
          • 1970-01-01
          • 2014-10-11
          • 2019-11-09
          • 1970-01-01
          • 2011-01-11
          • 1970-01-01
          相关资源
          最近更新 更多