我一直在寻找一种方法来确保在所有情况下都清除类的成员变量,例如类构造函数结束时的异常。
因为它们是成员变量,所以“尝试,捕获”和“使用”模式都没有用。 我注意到.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都是一个非常糟糕的解决方案。
最诚挚的问候,
卡斯滕
答案 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();
}
}
}