public void TestA()
{
using (var cn = new SqlConnection("Some String A"))
{
cn.Open();
}
}
...and...
public void TestB()
{
var cn = new SqlConnection("Some String B");
cn.Open();
cn.Dispose();
}
Now, for some of you, this may come as a shock to see the using keyword used anywhere else but the top of your source code file. Actually what it is doing is declaring a block between {} where the object defined in the () can be used (and by the way the object has to inherit from IDisposable). Once the code leaves the block the .Dispose() method is called on the object weather it leaves gracefully or via an exception being thrown. The object then becomes out of scope. Let’s have a look at the IL code that is created for these 2 functions.
.method public hidebysig instance void TestA() cil managed
{
.maxstack 2
.locals init (
[0] class [System.Data]System.Data.SqlClient.SqlConnection cn,
[1] bool CS$4$0000)
L_0000: nop
L_0001: ldstr "Some String A"
L_0006: newobj instance void [System.Data]System.Data.SqlClient.SqlConnection::.ctor(string)
L_000b: stloc.0
L_000c: nop
L_000d: ldloc.0
L_000e: callvirt instance void [System.Data]System.Data.Common.DbConnection::Open()
L_0013: nop
L_0014: nop
L_0015: leave.s L_0027
L_0017: ldloc.0
L_0018: ldnull
L_0019: ceq
L_001b: stloc.1
L_001c: ldloc.1
L_001d: brtrue.s L_0026
L_001f: ldloc.0
L_0020: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0025: nop
L_0026: endfinally
L_0027: nop
L_0028: ret
.try L_000c to L_0017 finally handler L_0017 to L_0027
}
.method public hidebysig instance void TestB() cil managed
{
.maxstack 2
.locals init (
[0] class [System.Data]System.Data.SqlClient.SqlConnection cn)
L_0000: nop
L_0001: ldstr "Some String B"
L_0006: newobj instance void [System.Data]System.Data.SqlClient.SqlConnection::.ctor(string)
L_000b: stloc.0
L_000c: ldloc.0
L_000d: callvirt instance void [System.Data]System.Data.Common.DbConnection::Open()
L_0012: nop
L_0013: ldloc.0
L_0014: callvirt instance void [System]System.ComponentModel.Component::Dispose()
L_0019: nop
L_001a: ret
}
public void TestC()
{
SqlConnection cn = null;
try
{
cn = new SqlConnection("Some String C");
cn.Open();
}
finally
{
cn.Dispose();
}
}
.method public hidebysig instance void TestC() cil managed
{
.maxstack 2
.locals init (
[0] class [System.Data]System.Data.SqlClient.SqlConnection cn)
L_0000: nop
L_0001: ldnull
L_0002: stloc.0
L_0003: nop
L_0004: ldstr "Some String C"
L_0009: newobj instance void [System.Data]System.Data.SqlClient.SqlConnection::.ctor(string)
L_000e: stloc.0
L_000f: ldloc.0
L_0010: callvirt instance void [System.Data]System.Data.Common.DbConnection::Open()
L_0015: nop
L_0016: nop
L_0017: leave.s L_0023
L_0019: nop
L_001a: ldloc.0
L_001b: callvirt instance void [System]System.ComponentModel.Component::Dispose()
L_0020: nop
L_0021: nop
L_0022: endfinally
L_0023: nop
L_0024: ret
.try L_0003 to L_0019 finally handler L_0019 to L_0023
}
public void TestA2()
{
try
{
using (var cn = new SqlConnection("Some String A"))
{
cn.Open();
}
}
finally
{
//do stuff
}
}
...and...
.method public hidebysig instance void TestA2() cil managed
{
.maxstack 2
.locals init (
[0] class [System.Data]System.Data.SqlClient.SqlConnection cn,
[1] bool CS$4$0000)
L_0000: nop
L_0001: nop
L_0002: ldstr "Some String A"
L_0007: newobj instance void [System.Data]System.Data.SqlClient.SqlConnection::.ctor(string)
L_000c: stloc.0
L_000d: nop
L_000e: ldloc.0
L_000f: callvirt instance void [System.Data]System.Data.Common.DbConnection::Open()
L_0014: nop
L_0015: nop
L_0016: leave.s L_0028
L_0018: ldloc.0
L_0019: ldnull
L_001a: ceq
L_001c: stloc.1
L_001d: ldloc.1
L_001e: brtrue.s L_0027
L_0020: ldloc.0
L_0021: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0026: nop
L_0027: endfinally
L_0028: nop
L_0029: nop
L_002a: leave.s L_002f
L_002c: nop
L_002d: nop
L_002e: endfinally
L_002f: nop
L_0030: ret
.try L_000d to L_0018 finally handler L_0018 to L_0028
.try L_0001 to L_002c finally handler L_002c to L_002f
}
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (1)
Commented:
The intent of Dispose is not to release the object being disposed from memory. Its intent is for the programmer to trigger the object to release any unmanaged resources that the object may be holding on to. IDisposable has no bearing over when the object itself will be purged from memory by the GC. The GC itself has no knowledge of unmanaged resources, which is why Dispose exists. Dispose is for the programmer to explicitly trigger the release of unmanaged resources; Finalize (what you referred to as a "destructor") is an implicit trigger of the same.
With regard to using statements, there are actually times when a using is counter-productive--though, most times it is advisable to use a using.