【发布时间】:2011-11-24 07:43:36
【问题描述】:
我有 2 个程序集,A 包含 Main 方法和 Foo 类,它们使用程序集 B 中的 Bar 类:
杆组件(组件 B):
public sealed class Bar : IDisposable {
/* ... */
public void Dispose() { /* ... */ }
}
Foo 类(程序集 A):
public class Foo : IDisposable {
private readonly Bar external;
private bool disposed;
public Foo()
{
Console.WriteLine("Foo");
external = new Bar();
}
~Foo()
{
Console.WriteLine("~Foo");
this.Dispose(false);
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing) external.Dispose();
disposed = true;
}
}
入口点(在程序集 A 中):
class Program
{
static void Main(string[] args)
{
try
{
var foo = new Foo();
Console.WriteLine(foo);
}
catch (FileNotFoundException ex)
{
// handle exception
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
}
这个软件的一个要求是它必须优雅地处理缺少 dll 的情况。
因此,当我删除程序集 B 并启动应用程序时,我希望 main 方法中的 try catch 块处理程序集 B 丢失时抛出的 FileNotFoundException。确实如此,但这就是问题开始的地方......
当应用程序继续时(在控制台中输入一行),Foo 类的终结器被调用(?!)虽然没有创建 Foo 的实例 - 构造函数还没有被调用。由于没有该类的实例,因此我无法在外部对实例调用 GC.SupressFinalize。在没有 B 程序集的情况下运行项目时,您在控制台输出中看到的唯一内容是 ~Foo。
所以问题:
- 为什么即使没有创建类的实例,也会调用终结器? (对我来说这完全没有意义!我很想开悟)
- 如果没有终结器中的 try-catch 块,是否可以防止应用程序崩溃? (这意味着重构整个代码库...)
一些背景知识:我在编写启用插件的企业应用程序时遇到了这个问题,要求它必须在插件部署文件夹中缺少 dll 并标记故障插件时继续运行。我认为外部插件加载过程周围的 try-catch 块就足够了,但显然它没有,因为在捕获第一个异常之后,终结器仍然被调用(在 GC 线程上),最终导致应用程序崩溃。
备注 上面的代码是我可以编写的用于在终结器中重现异常的最简单的代码。
备注 2 如果我在 Foo 构造函数中设置断点(在删除 Bar 的 dll 之后),它不会被命中。这意味着如果我在构造函数中设置一个创建关键资源的语句(在更新 Bar 之前),它将不会被执行,因此不需要调用终结器:
// in class Foo
public Foo() {
// ...
other = new OtherResource(); // this is not called when Bar's dll is missing
external = new Bar(); // runtime throws before entering the constructor
}
protected virtual void Dispose(bool disposing) {
// ...
other.Dispose(); // doesn't get called either, since I am
external.Dispose(); // invoking a method on external
// ...
}
备注3 一个明显的解决方案是像下面那样实现 IDisposable,但这意味着破坏参考模式的实现(甚至 FxCop 也会抱怨)。
public abstract class DisposableBase : IDisposable {
private readonly bool constructed;
protected DisposableBase() {
constructed = true;
}
~DisposableBase() {
if(!constructed) return;
this.Dispose(false);
}
/* ... */
}
【问题讨论】:
-
你发布了一个不好的例子。这甚至在进入 Main() 之前就崩溃了。当 jit 编译器无法为构造函数生成代码时,它会出错。 catch 子句无法捕获它。发布实际重现问题的代码。
-
@Hans Passant - 为我工作...... m0sa - 这只是一个摘录,但你需要你的终结器吗?在上面的代码中,您没有从该代码路径做任何事情(相关)。
-
我宁愿使用将您的 dll 编译成单个托管 dll 的工具。这也可以防止用户和错误的部署工具混合不同版本的 dll。
-
@ordag:当缺少带有 Bar 的程序集时它可以工作吗?
-
@m0sa:我们使用来自preemptive.com的dotfuscator,但也有一个名为ILmerge的免费工具:codeproject.com/KB/dotnet/mergingassemblies.aspx
标签: c# .net dll idisposable finalizer