Catching exceptions thrown in the constructor of the target object of a Using block

为君一笑 提交于 2020-01-24 04:17:09

问题


using(SomeClass x = new SomeClass("c:/temp/test.txt"))
{
...
}

Inside the using block, all is fine with treating exceptions as normal. But what if the constructor of SomeClass can throw an exception?


回答1:


Yes, this will be a problem when the constructor throws an exception. All you can do is wrap the using block within a try/catch block. Here's why you must do it that way.

using blocks are just syntactic sugar and compiler replaces each using block with equivalent try/finall block. The only issue is that the compiler does not wrap the constructor within the try block. Your code after compilation would have following conversion in the IL.

        //Declare object x of type SomeClass.
        SomeClass x;

        //Instantiate the object by calling the constructor.
        x = new SomeClass("c:/temp/test.txt");

        try
        {
            //Do some work on x.
        }
        finally
        {
            if(x != null)
                x.Dispose();
        }

As you can see from the code, the object x will not be instantiated in case when the constructor throws an exception and the control will not move further from the point of exception raise if not handled.

I have just posted a blog-post on my blog on this subject last night.

I'm just now wondering why C# designers did not wrap object construction within the try block which according to me should have been done.

EDIT

I think I found the answer why C# does not wrap object construction into try block generated in place of the using block.

The reason is simple. If you wrap both declaration and instantiation within the try block then the object would be out of scope for the proceeding finally block and the code will not compile at because, for finally block the object hardly exists. If you only wrap the construction in the try block and keep declaration before the try block, even in that case the it will not compile since it finds you're trying to use an assigned variable.




回答2:


Put your using into the try catch f.e.

try
{
   using(SomeClass x = new SomeClass("c:/temp/test.txt"))
   {
       ...
   }
}
catch(Exception ex)
{
   ...
}



回答3:


I threw a quick test program together to check this, and it seems that the Dispose method does not get called when an exception is thrown in the constructor;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            using (OtherClass inner = new OtherClass())
            {
                Console.WriteLine("Everything is fine");
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
}

class OtherClass : IDisposable
{
    public OtherClass()
    {
        throw new Exception("Some Error!");
    }

    void IDisposable.Dispose()
    {
        Console.WriteLine("I've disposed my resources");
    }
}

Output :

Some Error!

If you don't throw the exception..

Output :

Everything is fine

I've disposed my resources

Presumably this is because the object was never created, so there's nothing to call Dispose on.

I'm not sure what would happen if the constructor had already allocated some resources which would normally require a proper clean up through Dispose and the exception occurred afterwards though.




回答4:


This should not be a problem with a well-designed class. Remember the overall question:

public class HoldsResources : IDisposable
{
    public HoldsResources()
    {
        // Maybe grab some resources here
        throw new Exception("whoops");
    }
}

using (HoldsResources hr = new HoldsResources())
{
}

So, the question is, what should you do about the resources allocated by the HoldsResources constructor before it threw an exception?

The answer is, you shouldn't do anything about those resources. That's not your job. When it was decided that HoldsResources would hold resources, that brought the obligation to properly dispose of them. That means a try/catch/finally block in the constructor, and it means proper implementation of IDisposable to dispose of those resources in the Dispose method.

Your responsibility is to use the using block to call his Dispose method when you're through using the instance. Nothing else.




回答5:


When you get a hold of resources in the ctor that are not subject to garbage collection, you have to make sure to dispose of them when things go south.

This sample shows a ctor which will prevent a leak when something goes wrong, the same rules apply when you allocate disposables inside a factory method.

class Sample
{
  IDisposable DisposableField;

  ...

  public Sample()
  {
    var disposable = new SomeDisposableClass();
    try
    {
       DoSomething(disposable);
       DisposableField = disposable;
    }
    catch
    {
       // you have to dispose of it yourself, because
       // the exception will prevent your method/ctor from returning to the caller.
       disposable.Dispose();
       throw;
    }
  }
}

Edit: I had to change my sample from a factory to a ctor, because apparantly it wasn't as easy to understand as I hoped it would be. (Judging from the comments.)

And of course the reason for this is: When you call a factory or a ctor, you can only dispose of its result. When the call goes through, you have to assume that everything's okay so far.

When calling a ctor or factory you don't have to do any reverse-psychoanalysis to dispose of anything you can't get hold of anyways. If it does throw an exception, it is in the factories/ctor's responsibility to clear anything half-allocated before re-throwing the exception. (Hope, this time, it was elaborate enough...)



来源:https://stackoverflow.com/questions/3360860/catching-exceptions-thrown-in-the-constructor-of-the-target-object-of-a-using-bl

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!