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

时间:2012-08-15 13:40:42

标签: c# .net il raii

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

因为它们是成员变量,所以“尝试,捕获”和“使用”模式都没有用。 我注意到.NET C ++(C ++ / clr:safe)提供了智能指针(称为msclr :: auto_handle)的仿真,例如auto_ptr或shared_ptr。这非常有用,因为我可以以非常干净的方式确定性地破坏有限的资源,如线程或套接字。

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

我已将感兴趣的人列入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#中获得相同的功能吗? 我们已经确认使用无法工作,因为它位于成员变量上,并且每个函数中的try / catch都是一个非常糟糕的解决方案。

最诚挚的问候,

卡斯滕

2 个答案:

答案 0 :(得分:4)

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

auto_handle类的析构函数调用它包装的对象的析构函数。在与C ++的相似性结束的地方,C ++ / CLI类的析构函数是IDisposable.Dispose()方法。 &#34;真实&#34;析构函数是类的终结器,用!classname语法表示。

与C#的相似性开始,它是使用语句的完全等价物。它也确保调用Dispose()方法,并使用try / finally确保即使存在异常也会发生。设计人员只选择选择C ++程序员更熟悉的语法,而不是将使用关键字添加到C ++ / CLI。在IDisposable的使用中也可以看到,你不能调用Dispose()但必须使用 delete 运算符来调用它。

所以如果你喜欢auto_handle&lt;&gt;在C ++ / CLI中,您在C#中使用使用的原因完全相同。

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

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

答案 1 :(得分:1)

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

class junk
{
    private IDisposable somevar;
    void SomeFunc()
    {
        using (somevar = SomeOtherFunc())
        {
           YesAnotherFunc();
        }
    }
}