在.NET中从构造函数中抛出异常

12

如果我从构造函数中抛出异常,是否会导致内存泄漏?例如以下代码:

class Victim
{
    public string var1 = "asldslkjdlsakjdlksajdlksadlksajdlj";

    public Victim()
    {
        //throw new Exception("oops!");
    }
}

这些失败的对象会被垃圾收集器回收吗?


几乎无关,但有用的提示:注意在控件构造函数中抛出的异常。这可能会破坏控件/表单的设计器。我通过编写一个Initialize()方法并从外部调用来解决了这个问题(但我不喜欢这种方式)。 - Neil Barnwell
5个回答

27

从不泄漏内存的角度来看,这通常是安全的。但如果您在类型中分配非托管资源,则从构造函数抛出异常是危险的。请看以下示例:

public class Foo : IDisposable { 
  private IntPtr m_ptr;
  public Foo() {
    m_ptr = Marshal.AllocHGlobal(42);
    throw new Exception();
  }
  // Most of Idisposable implementation ommitted for brevity
  public void Dispose() {
    Marshal.FreeHGlobal(m_ptr);
  }
}

即使您使用using块,每次尝试创建时,此类也会泄漏内存。例如,这会导致内存泄漏。

using ( var f = new Foo() ) {
  // Won't execute and Foo.Dispose is not called
} 

需要明确的是,using语句块中的代码并不会被执行,同时Foo类的.Dispose()方法也不会被调用,因为没有创建实例来调用.Dispose()方法。 - jrista
我来晚了,但是在构造函数中加入try-finally语句不就可以轻松解决这个问题吗? - Gusdor
@Gusdor:最终解决这个问题的方法是使用另一个一次性类来包装非托管资源,该类在其构造函数中除了分配资源之外什么也不做。即使Foo在构造函数中抛出异常,资源包装对象仍然可以被收集并适当地释放资源(尽管不是立即释放)。 - Chris Charabaruk

12

如果您在构造函数中没有创建非托管资源,那么从构造函数中抛出异常应该是可以的。但是,如果您在构造函数中创建了非托管资源,则包括throws在内的整个构造函数体都应该被try/catch包裹起来。以下是JaredPar的一个很好的例子:

public class Foo : IDisposable { 
  private IntPtr m_ptr;
  public Foo() {
    try
    {
        m_ptr = Marshal.AllocHGlobal(42);
        throw new Exception();
    }
    catch
    {
        Dispose();
        throw;
    }
  }
  // Most of Idisposable implementation ommitted for brevity
  public void Dispose() {
    Marshal.FreeHGlobal(m_ptr);
  }
}

以下内容现在可以正常工作:
using ( var f = new Foo() ) {
  // Won't execute, but Foo still cleans itself up
}

4
有趣的是,昨天我帮忙回答了一个类似的问题。
如果你有一个派生类型,这就成为了一个更大的问题,因为某些部分会被初始化而另一些则不会。从内存的角度来看,这并不真的重要,因为垃圾收集器知道每个东西在哪里。但如果你有任何非托管资源(实现IDisposable),事情可能会变得混乱。

2
是的,垃圾回收器会回收已经分配在对象中的托管资源。如果您初始化了任何非托管资源,则需要按照常规方式自行清理这些资源。

1

这取决于在抛出异常之前您已获取了哪些其他资源。我认为在构造函数中抛出异常并不是很好,但是在 finalizer 或 dispose() 中抛出异常要糟糕得多。


2
将它们扔进 finalizers 或 dispose() 中会更加糟糕。例如,如果 FileStream 无法刷新流(例如由于网络错误),则会抛出异常。尽可能避免抛出异常,但如果必须这样做,请这样做。在 FileStream 的情况下,吞掉异常比抛出异常更加危险,因为这会让调用者误以为文件已经成功写入。 - Joe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接