在C#中实现C ++ / clr的auto_handle功能

Kar*_*sen 0 .net c# il raii

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

因为它们是成员变量,所以“尝试,捕获”和“使用”模式没有用。我注意到.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!
  }
Run Code Online (Sandbox Code Playgroud)

...变成...

  .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
Run Code Online (Sandbox Code Playgroud)

是否有类似的方法可以使用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.
        }
  };
Run Code Online (Sandbox Code Playgroud)

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

最好的祝福,

卡斯滕

Han*_*ant 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运算符来调用它。

所以,如果你喜欢auto_handle <在C ++ / CLI>那么你就会有相同的原因,就像在C#。

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

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