Tuesday, May 8, 2012

A Dispose Gone Bad

I'm going through some VB.NET source code and I keep seeing a common bad occurrence.  Below is a slightly modified example of the code I keep seeing.
Code Snippet
  2. Dim SqlDataAdapter As New SqlClient.SqlDataAdapter
  3. Dim sqlCommand As New SqlClient.SqlCommand
  4. Dim dt_dataadapter As New DataSet
  6. 'Set Parameter   
  7. With sqlCommand
  8.     .Connection = dataConnection
  9.     .CommandType = CommandType.StoredProcedure
  10.     .CommandText = "blah blah"
  11.     .Parameters.Add("@ID", SqlDbType.Int, 8, "ID").Value = 0
  12. End With
  13. SqlDataAdapter.SelectCommand = sqlCommand
  14. SqlDataAdapter.Fill(dt_dataadapter)
  16. If dt_dataadapter Is Nothing Then
  17.     dt_dataadapter.Dispose()
  18.     SqlDataAdapter.Dispose()
  19.     sqlCommand.Dispose()
  20.     MessageBox.Show("Failed retrieving 'blah blah'.")
  21.     Exit Sub
  22. End If
  23. If dt_dataadapter.Tables.Count = 0 Then
  24.     dt_dataadapter.Dispose()
  25.     SqlDataAdapter.Dispose()
  26.     sqlCommand.Dispose()
  27.     MessageBox.Show("Failed retrieving 'blah blah'.")
  28.     Exit Sub
  29. End If
  30. If dt_dataadapter.Tables(0).Rows.Count = 0 Then
  31.     dt_dataadapter.Dispose()
  32.     SqlDataAdapter.Dispose()
  33.     sqlCommand.Dispose()
  34.     MessageBox.Show("Failed retrieving 'blah blah'.")
  35.     Exit Sub
  36. End If

This same format is being used in multiple locations.  There's probably a lot your seeing wrong with this right now, but if not then that's why you should read the rest of this article.  Let me go through this step by step. 

The developer is using objects that use the interface IDispose.  The second thing you'll notice is there is not one try-catch in this entire section.  If the connection mysteriously disappears the program will terminate immediately.  The end user will have no idea what's going on.  Crash!

Now let's say it gets past that.  Maybe he/she finally put a try-catch around the Fill() function.  The dataset has an exception, but the catch just says a message and it continues on.  If the dt_dataadapter is nothing it then goes and disposes of the dt_dataadapter.  You can't dispose nothing.  So now this results in the application crashing.

Let's choose one more scenario.  Let's say this function is called and is surrounded in a Try-Catch.  Now the application doesn't crash, however the data adapter and the command is never released from memory.  Now you have a memory crisis on your hands.  If this happens enough then the application will crash.

The appropriate and best way to avoid everything that has happened above is by using the Using statement.  The example below is about 1,000 times better, easier to read, and this won't cause any memory leakage.
Code Snippet
  2. Using Command As New SqlClient.SqlCommand("blah, blah", dataConnection)
  3.     Command.Parameters.AddWithValue("@ID", 0)
  5.     Using Adapter As New SqlClient.SqlDataAdapter(Command)
  6.         Using Data As New DataSet
  7.             Try
  8.                 Adapter.Fill(Data)
  9.             Catch ex As Exception
  10.                 MessageBox.Show("Failed retrieving 'blah blah'.")
  11.                 Exit Sub
  12.             End Try
  14.             If Not Data Is Nothing AndAlso Data.Tables.Count > 0 AndAlso Data.Tables(0).Rows.Count > 0 Then
  15.                 ' Do something with the data here.
  16.             Else
  17.                 MessageBox.Show("Failed retrieving 'blah blah'.")
  18.                 Exit Sub
  19.             End If
  20.         End Using
  21.     End Using
  22. End Using

This works because whenever this method is completed it will automatically call the dispose for each object that uses the Using statement.

If you see anything wrong, or have any questions, please leave a message.

No comments:

Post a Comment