0

我一直在寻找一种方法来确保在所有情况下都清理类的成员变量,例如类构造函数末尾的异常。

因为它们是成员变量,所以“try、catch”和“using”模式没有用处。我注意到 .NET C++ (C++ /clr:safe) 提供了智能指针(称为 msclr::auto_handle)的模拟,例如 auto_ptr 或 shared_ptr。这非常有用,因为我可以以非常干净的方式对有限资源(例如线程或套接字)进行确定性销毁。

我一直在分析使用 C++ /clr 生成的 IL,并注意到它实际上似乎所做的只是在修改封装数据的每个函数中使用 try/faults 向 IL 发送垃圾邮件。

我已经为任何感兴趣的人提供了 IL 列表。(try/fault 不是我添加的,是 C++/clr 编译器添加的)

  MyClass()
  {
        myDisposable.reset(gcnew MyDisposable());
        throw gcnew Exception("Hello World");
        // myDisposable needs to clean up now
        // because it is very large or locks a limited resource.
        // Luckily with RAII.. it does!
  }

……变成……

  .try
  {
  IL_0006:  ldarg.0
  IL_0007:  ldloc.0
  IL_0008:  stfld      class msclr.'auto_handle<MyDisposable>' modreq([mscorlib]System.Runtime.CompilerServices.IsByValue) MyClass::myDisposable
  IL_000d:  ldarg.0
  IL_000e:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0013:  ldarg.0
  IL_0014:  ldfld      class msclr.'auto_handle<MyDisposable>' modreq([mscorlib]System.Runtime.CompilerServices.IsByValue) MyClass::myDisposable
  IL_0019:  newobj     instance void MyDisposable::.ctor()
  IL_001e:  call       instance void msclr.'auto_handle<MyDisposable>'::reset(class MyDisposable)
  IL_0023:  ldstr      "Hello World"
  IL_0028:  newobj     instance void [mscorlib]System.Exception::.ctor(string)
  IL_002d:  throw
  IL_002e:  leave.s    IL_003c
  }  // end .try
  fault
  {
  IL_0030:  ldarg.0
  IL_0031:  ldfld      class msclr.'auto_handle<MyDisposable>' modreq([mscorlib]System.Runtime.CompilerServices.IsByValue) MyClass::myDisposable
  IL_0036:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
  IL_003b:  endfinally
  }  // end handler

是否有类似的方法可以使用 C# 实现这一点,因为我的软件将非常复杂,而且我自己处理这一切将非常危险且容易出错。那么有没有人知道可以自动添加这个额外的 IL 代码以便我可以用 C# 模拟 RAII 的技术甚至构建后步骤?

编辑:(另一个例子)

  ref class MyClass
  {
  private:
        msclr::auto_handle<MyDisposable> myDisposable;

  public:
        MyClass()
        {
              myDisposable.reset(gcnew MyDisposable());
              throw gcnew Exception("Hello World");
              // myDisposable needs to clean up now because it is very large or locks a limited resource.
        }
  };

myDisposable 是一个成员变量。当从构造函数中抛出“Hello World”时,myDisposable 实际上会立即被释放。我可以在 C# 中获得相同的功能吗?我们已经确认 using 无法工作,因为它位于成员变量上,并且每个函数中的 try/catch 是一个非常糟糕的解决方案。

此致,

卡斯滕

4

2 回答 2

5

C++/CLI 中的 auto_handle<> 模板类使用了一个特定于 C++/CLI 编译器的功能,称为“堆栈语义”。C++ 程序员非常熟悉的一个特性以及 RAII 背后的核心运行时支持。简而言之,编译器确保在作用域块的末尾调用析构函数。您会看到 IL 中发出的 .try/fault 块,以确保即使在代码引发异常时也调用析构函数。

auto_handle 类的析构函数调用它包装的对象的析构函数。这就是与 C++ 的相似性结束的地方,C++/CLI 类的析构函数是 IDisposable.Dispose() 方法。“真正的”析构函数是类的终结器,由!classname语法表示。

并且与 C# 的相似之处开始,它完全等同于using语句。它也确保调用 Dispose() 方法,并且还使用 try/finally 来确保即使有异常也会发生。设计者没有在 C++/CLI 中添加using关键字,而是简单地选择了 C++ 程序员更熟悉的语法。在 IDisposable 的使用中也可以看到,您不能调用 Dispose(),但必须使用delete运算符来调用它。

因此,如果您喜欢 C++/CLI 中的 auto_handle<>,那么您将有完全相同的理由喜欢在 C# 中使用。

请注意 C++ 中的 RAII 与托管代码中的 auto_handle/using 之间的巨大差异。您经常需要 RAII 来释放 C++ 中的内存,这在托管语言中是完全没有必要的。你也不能,这是垃圾收集器的工作。只有在创建继承 IDisposable 的对象时才应该使用using 。当然不是 .NET 中的每个类都可以。它也是可选的,不是必需的,类的终结器始终确保在未使用 Dispose() 及早完成时释放非托管资源。

在特殊情况下,由于严格的异常处理或跟踪对象生命周期的痛苦太大,跳过 Dispose() 调用当然是可以的。.NET 框架中此类的一个标准示例是 Thread 类。它有 5 个一次性原生资源,但没有实现 IDisposable。因为编写代码以确保它被调用往往会破坏使用线程的意义。

于 2012-08-15T17:05:25.877 回答
1

您可以在 using 中使用成员变量:

class junk
{
    private IDisposable somevar;
    void SomeFunc()
    {
        using (somevar = SomeOtherFunc())
        {
           YesAnotherFunc();
        }
    }
}
于 2012-08-15T17:04:27.343 回答