tags:

views:

246

answers:

6

Re-initialization within "using" block is a bad idea, to be avoided at all times. Still i am going to ask this:

Why does "using" call dispose on the original value and not on the last reference or re-initialization (which happens if try finally block is used)

MyClass b = new MyClass();// implements Idisposable
MyClass c = new MyClass();
MyClass a ; 

 using (a = new MyClass())
 {
                a = b;
                a = c;
 }

In the above code dispose will be called on original reference and not the newer referenced. This can be easily verified by printing something on console in the dispose method.

However with try{} finally code the last reference dispose method is called.

try
{
   a = new MyClass();
   a = b;
   a = c;
 }
  finally 
   {
   a.Dispose();
  }

MSDN : The using statement ensures that Dispose is called even if an exception occurs while you are calling methods on the object.

using (Font font1 = new Font("Arial", 10.0f)) 
{
    byte charset = font1.GdiCharSet;
}

Basically "using" translates to:

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}
+3  A: 

The using can be seen as a promise to call disposed on the object declared with using. This is the only thing that, IMHO, makes sense!

If you would call dispose on the reassigned value, then the original value will not be disposed.

Obalix
This is a nice phrasing, and correctly explains the observed results.
Noon Silk
A: 

Dispose will be called on the object referenced in the argument of the using clause. It's that simple.

TheSoftwareJedi
using translates to try finally so shouldn't it call the last reference dispose?
PRR
+1  A: 

It seems that the "using" is creating it's own variable to store the reference, that gets initialized to "a" which in turn is initialized to the first instance of the object. Later when you change "a", you're not really changing the original reference that was stored in the "using" statement. It seems like a pretty good feature, since the using is responsible for disposing the actual object mentioned in the using statement, not the variable.

Mike Mooney
+5  A: 

There are two forms of using statements defined in the C# specification:

using-statement:
    using   (    resource-acquisition   )    embedded-statement
resource-acquisition:
    local-variable-declaration
    expression

If you have a local-variable-declaration, there wouldn't be any questions. The variable will be read-only in the using block and you can't change it at all. The spec says:

8.13 The using statement

[...] In either expansion, the resource variable is read-only in the embedded statement.

Here, we're dealing with the second form: where resource-acquisition is expression and not a local-variable-declaration. In which case, the C# spec clearly says:

A using statement of the form

 using (expression) statement

has the same two possible expansions, but in this case ResourceType is implicitly the compile-time type of the expression, and the resource variable is inaccessible in, and invisible to, the embedded statement. [emphasis mine]

Obviously, you can't change an invisible, inaccessible variable. Its value is assigned only in the using resource-acquisition clause. Therefore, it'll have the old value of the variable, not the new one.

When you are dealing with an assignment to an already declared variable, you are using this form of the using statement. The fact that you are assigning a value to a variable like

using ( x = something )

is irrelevant. The whole x = something is treated as an expression and only the value of that expression is what matters. It's important to know that "resource variable" is not "x" here. It's an invisible variable. From the compiler's perspective, there's not much difference between the following constructs:

using ( something ) 
using ( x = something )
using ( y = x = something )

In all cases, the expression will get executed and the value is what will get guaranteed disposal, not the variable. What would the compiler supposed to do if this wasn't the defined behavior and you had written the third line in the above block? Dispose x? y? both? neither? The current behavior makes sense.

Mehrdad Afshari
@Downvoter: Care to explain? I'm surprised as I've quoted the reference here. Your justification would be definitely interesting to hear since it's an argument against the spec.
Mehrdad Afshari
It's not me, but your explanation is a little ... "dry". Anyone can read from the spec.
Noon Silk
@silky: I don't think being "dry" have anything to do with correctness. I believe, dry or not, it's the only thing that makes sense here. Specification is the only authoritative source for this question and **mandates** such a behavior; everything else is speculation. Clearly, the language could choose to have its `using` statement implemented by another method as it's not a technical limitation but a design decision.
Mehrdad Afshari
@Mehrdad: Yes, I understand viewpoint. Nevertheless, your analysis remains "dry" to me, though it is correct. This cannot be disputed, as it's my opinion. There is little point arguing with me about it, only you can decide how much it means to you. (I don't expect it to mean anything, I was merely trying to help you understand why you may have been downvoted).
Noon Silk
@silky: I'm not arguing with you. Thanks for your comment. Simply meant to say specifications are inherently "dry".
Mehrdad Afshari
+1  A: 

Yeah, it's interesting.

So I looked at the decompiled code:

  IL_0000:  nop
  IL_0001:  newobj     instance void ConsoleApplication17.Foo1::.ctor()
  IL_0006:  dup
  IL_0007:  stloc.0
  IL_0008:  stloc.1 // 1. note this
  .try
  {
    IL_0009:  nop
    IL_000a:  newobj     instance void ConsoleApplication17.Foo2::.ctor()
    IL_000f:  stloc.0 // 2. and this
    IL_0010:  nop
    IL_0011:  leave.s    IL_0023
  }  // end .try
  finally
  {
    IL_0013:  ldloc.1 // 3. and this
    IL_0014:  ldnull
    IL_0015:  ceq
    IL_0017:  stloc.2
    IL_0018:  ldloc.2
    IL_0019:  brtrue.s   IL_0022
    IL_001b:  ldloc.1
    IL_001c:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0021:  nop
    IL_0022:  endfinally
  }  // end handler
  IL_0023:  nop

So it really copies the reference, and uses it's copy later, allowing you to reset the variable, without really achieving anything, in terms of the finaliser.

Really, it would be nice if you could only use 'read-only' variables in the using statement. It's a little confusing. And certainly, the MSDN is misleading.

Noon Silk
+3  A: 

The compiler generates this code:

MyClass b = new MyClass();
MyClass a;
MyClass cs$3$000 = a = new MyClass();
try {
  a = b;
}
finally {
  if (cs$3$000 != null) cs$3$000.Dispose();
}

The auto-generated cs$3$000 local variable implements the contract.

Hans Passant