Staying On The Correct Path

In a current task where I had to consolidate the structure of a website and organize the files better, I ran across this interesting quirk.  When developing, we work in a virtual directory on our local machine, while on a real server, it runs in the root directory.  This poses problems whenever we want to use relative paths to images, style sheets, etc.

The key to fixing this is the VirtualPathUtility, which has the logic in it to determine the root of the web application and return a proper path.  I thought it would be easy to just use this object right in the markup like:

<head runat="server">
    <title></title>
    <link id="cssLinkOutside" href='<%=System.Web.VirtualPathUtility.ToAbsolute("~/StyleSheet.css")%>' rel="stylesheet" type="text/css" />
</head>

That didn’t work.  The rendered HTML turned out to be:

<link id="cssLinkOutside" href="&lt;%=System.Web.VirtualPathUtility.ToAbsolute(&quot;~/StyleSheet.css&quot;)%>" rel="stylesheet" type="text/css" />

Pretty literal.  The cause of this odd behavior is that the HEAD tag is set to runat=”server”.  If that piece is taken out, the embedded code works well.  So if you need the HEAD to be a server control – like when using themes – then what?  You can add code in the code-behind to do an attributes.add() on the LINK tag.  But having to add code in two places for such a simple need is just too much, especially when I was looking at updating a couple dozen pages.

The workaround is kind of surprising: Wrap the tag that contains dynamic code in a placeholder.

<head runat="server">
    <title></title>
    <asp:PlaceHolder runat="server" id="holder">
        <link id="cssLinkInside" href='<%=System.Web.VirtualPathUtility.ToAbsolute("~/StyleSheet.css")%>' rel="stylesheet" type="text/css" />
    </asp:PlaceHolder>
</head>

This renders the tag properly:

<link id="cssLinkInside" href='/WebTestbed/StyleSheet.css' rel="stylesheet" type="text/css" />

They Thought They Could Stop Me.

I was working with a DataGrid that had a ButtonColumn in it.  I had a need to set the CommandArgument for this button.  Did you know there is no way to set a CommandArgument for a ButtonColumn?

image21

I was all prepared to grab that control and set that property in the ItemDataBound event, but it doesn’t seem to exist.  Most people would resort to a template column, stick a button in it and work on that control.  Problem was, I was doing everything in code with no markup.  That adds a little complexity to that alternative.

Setting a simple breakpoint in the ItemDataBound event, I looked a little closer at what I had to work with in the Immediate window.

? e.Item.Controls(3)
{System.Web.UI.WebControls.TableCell}
     System.Web.UI.WebControls.TableCell: {System.Web.UI.WebControls.TableCell}
? e.Item.Controls(3).Controls.Count
1
? e.Item.Controls(3).Controls(0)
{Text = “Edit”}
     System.Web.UI.WebControls.DataGridLinkButton: {Text = “Edit”}

Hmm, it’s a DataGridLinkButton.  And it does have a CommandArgument property.  So let’s find that control and cast to that type and set that property.

image_thumb22

I see.  So this type is not user-accessible.  It doesn’t even show up in the Object Browser.  However, it does show up in Reflector, and I can see that it inherits from LinkButton, which is public.  Let’s whip up a quick function to find that control and return a LinkButton for setting the CommandArgument.

Woah, slow down a bit.  This is a ButtonColumn and it can be a link button, command button, or an image button.  If we have a function specifically for LinkButton, it’s going to potentially error out.  In the typical, excellent design of the .NET framework, these three button types are all related using the IButtonControl interface, which has properties for CommandName and CommandArgument.  So by using the interface instead of the exact type, we’re being safe and future-proofing ourselves against other button types.

Private Function GetButtonColumnButton(row As DataGridItem, commandName As String) As IButtonControl
    Return RecurseRowControls(row, commandName)
End Function

Private Function RecurseRowControls(ctl As WebControl, commandName As String) As IButtonControl
    Dim btn As IButtonControl

    ' loop through embedded controls
    For Each c As WebControl In ctl.Controls
        btn = TryCast(c, IButtonControl)

        ' if it is a button and the command name matches, return it
        If btn IsNot Nothing AndAlso String.Compare(btn.CommandName, commandName, True) = 0 Then
            Return btn
        End If

        ' if the control has child control, search them for the button
        If c.HasControls Then
            btn = RecurseRowControls(c, commandName)
            If btn IsNot Nothing Then Return btn
        End If
    Next

    ' no button found
    Return Nothing

End Function

And just like that, we can now have access to the button’s properties like Text, CommandName, CommandArgument, and CausesValidation.  That’s some great stuff there.

Private Sub Grid_ItemDataBound(sender As Object, e As DataGridItemEventArgs) Handles Me.ItemDataBound
    Dim btn As IButtonControl

    If e.Item.ItemType = ListItemType.AlternatingItem Or e.Item.ItemType = ListItemType.Item Then
        btn = GetButtonColumnButton(e.Item, "Edit")
        btn.CommandArgument = "something like an ID"
        btn.Text = "specific text label"
    End If
End Sub

.

Now This is Something Worth Looking Into

When you see something like this in the call stack, you have to know more:

[InvalidOperationException: This SqlTransaction has completed; it is no longer usable.]
   System.Data.SqlClient.SqlTransaction.ZombieCheck() +1623536
   System.Data.SqlClient.SqlTransaction.Rollback() +172
   Framework.Common.Database.RollbackTransaction() in C:\Framework\Common\Database.vb:413 
   Test.uxCreate_Click(Object sender, EventArgs e) in C:\Test.aspx.vb:47
   System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +154
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3691

And the error message is interesting as well: “This SqlTransaction has completed; it is no longer usable”.

A little background on what is going on: this is a database class that is managing a SQL transaction to be used by multiple objects.  The database object instance gets passed along to the different objects and they use it, participating in the transaction.  Somewhere along the way, maybe a commit is occurring and then the transaction becomes invalid.  That’s how it seems.

Tossing in a bunch of debug.writelines to see what what happening inside the methods popped up a message about a SQLException being raised because of a non-existent column name.  Fixing the schema problem fixed the zombie problem.  But what was the reason for the original error?

Let’s say you bring a sandwich into work.  You give that sandwich to someone and tell them to put it in the refrigerator for you.  This person (the fridgemaster) puts it in the fridge and comes back to the refrigerator a little later to find your sandwich moldy and spoiled, so he throws the sandwich out.  Later on yet, you go to the fridgemaster and ask for your sandwich.  He says “this sandwich has spoiled; it is no longer usable.”  In response to your puzzled look, the fridgemaster says “I did a ZombieCheck and it was moldy.”

In coding terms, the order of events was: TX started, SQL error occurred, TX rolled back (by SQL Server), SQLException raised and caught, TX rolled back in catch block (by user code) and unable to because the TX already was rolled back by SQL Server.  This rollback behavior is dependent on the severity of the error. In the case of the schema error, which presumably was interpreted as “this will NEVER work”, this equated to being severe enough to roll back the transaction.

Small Changes Make Big Changes

I read couple small tips from a SQL Server MVP that I had to try out.  I was surprised this is the first I’d ever heard of them.  The tips were concerning optional parameters – basically, when you pass a default to match everything or a specific value to match.  Like all things programming, there’s plenty of ways to accomplish the same results.  The difference is how well they perform.

Take an example where you are optionally filtering a table by VendorID, NULL means match everything.  The two ways I’d done this previously are:

and (@vendorid is null or vendorid=@Vendorid)

and

and vendorid=isnull(@Vendorid,vendorid)

Only subtly different.  I had always worried about the second option because I’d read that using functions ruins chances of optimization using indexes, but it turns out to not be the case.  The new suggestion by the MVP was to use BETWEEN and COALESCE the NULL default value into an upper and lower bound.  So, storing a high value number like 999999999 in a variable called @MaxInt, the clause looks like

and vendorid between coalesce(@VendorID,0) and coalesce(@VendorID,@MaxInt)

But how does it perform?  Interestingly well. When using an ID the query plans were:

57% of batch using the OR construct

image16

vs. 53% of batch using ISNULL

image17

vs. 26% of batch using COALESCE

image18

When using NULL as the value, the query plans were the same and the batch percent was different but relative: 42%, 38%, and 16%.

The I/O cost was significantly lower when using the BETWEEN and, as shown, there’s less complexity and it results in a lower cost to the overall batch.

The other tip was another use of COALESCE.  Instead of:

and (@streetnumber is null or streetnumber like '%'+@StreetNumber+'%')

use:

and streetnumber like coalesce('%'+@StreetNumber+'%','%')

In my testing, that single change took the statement from 68% of the batch to 60% of the batch.  After changing all the criteria, the whole query went from an unusable 25 seconds to a manageable 4 seconds.

Saving Objects–Simple, Not Difficult

XML can be a wonderful thing.  Storing XML in SQL Server can be wonderful, too.  It gives you a place to store a lot of data in one field and is especially useful if that data is considered one unit of data.  A good example of this would be storing user preferences, for example, a stripped down table like:

image19

So that’s one field: preferences.  Now let say that we have some items we want to store, like a few general preferences and some dialog boxes where the user checked the “Do not show this message again” option:

Public Class UserPreferences

    Private _UserID As Integer
    Private _General As GeneralPreferences
    Private _DoNotShowMessages As DoNotShowMessagesPreferences

    Public Property General As GeneralPreferences
        Get
            If _General Is Nothing Then _General = New GeneralPreferences
            Return _General
        End Get
        Set(ByVal value As GeneralPreferences)
            _General = value
        End Set
    End Property

    Public Property DoNotShowMessages As DoNotShowMessagesPreferences
        Get
            If _DoNotShowMessages Is Nothing Then _DoNotShowMessages = New DoNotShowMessagesPreferences
            Return _DoNotShowMessages
        End Get
        Set(ByVal value As DoNotShowMessagesPreferences)
            _DoNotShowMessages = value
        End Set
    End Property

    Public Sub New()

    End Sub

    Private Sub New(ByVal userID As Integer)
        _UserID = userID
    End Sub

    Public Class GeneralPreferences
        Public Property ShowSplashScreen As Boolean
        Public Property UseAlternateColorScheme As Boolean
        Public Property NumberOfItemsinGrids As Integer
    End Class

    Public Class DoNotShowMessagesPreferences
        Public Property HideNoResultsMessage As Boolean
        Public Property HideCloseConfirmation As Boolean
    End Class
End Class

This gives us two nested classes that store our values in nice groups, held in a class that allows us to access those nested classes and set the values.  Now we want to create an XML document that we can save and load in SQL.  So saving would be something like:

    Public Sub SaveXML()
        Dim doc As Xml.XmlDocument
        Dim parentNode As XmlNode
        Dim childNode As XmlNode
        Dim parms As New Generic.List(Of SqlClient.SqlParameter)

        doc = New XmlDocument
        doc.LoadXml("<UserPreferences />")

        parentNode = doc.DocumentElement.AppendChild(doc.CreateNode(XmlNodeType.Element, "General", doc.NamespaceURI))

        childNode = parentNode.AppendChild(doc.CreateNode(XmlNodeType.Element, "ShowSplashScreen", doc.NamespaceURI))
        childNode.InnerText = Me.General.ShowSplashScreen.ToString

        childNode = parentNode.AppendChild(doc.CreateNode(XmlNodeType.Element, "UseAlternateColorScheme", doc.NamespaceURI))
        childNode.InnerText = Me.General.UseAlternateColorScheme.ToString

        childNode = parentNode.AppendChild(doc.CreateNode(XmlNodeType.Element, "NumberOfItemsInGrids", doc.NamespaceURI))
        childNode.InnerText = Me.General.NumberOfItemsInGrids.ToString

        parentNode = doc.DocumentElement.AppendChild(doc.CreateNode(XmlNodeType.Element, "DoNotShowMessages", doc.NamespaceURI))

        childNode = parentNode.AppendChild(doc.CreateNode(XmlNodeType.Element, "HideNoResultsMessage", doc.NamespaceURI))
        childNode.InnerText = Me.DoNotShowMessages.HideNoResultsMessage.ToString

        childNode = parentNode.AppendChild(doc.CreateNode(XmlNodeType.Element, "HideCloseConfirmation", doc.NamespaceURI))
        childNode.InnerText = Me.DoNotShowMessages.HideCloseConfirmation.ToString

        With parms
            .Add(New SqlClient.SqlParameter("@UserID", _UserID))
            .Add(New SqlClient.SqlParameter("@Preferences", doc.OuterXml))
        End With

        SqlHelper.ExecuteNonQuery(CONN_STRING, CommandType.Text, _
            "insert userpreferences(userid,preferences) values(@UserID,@Preferences)", parms.ToArray)

    End Sub

and would give us an xml document saved to the server like:

<UserPreferences>
  <General>
    <ShowSplashScreen>True</ShowSplashScreen>
    <UseAlternateColorScheme>False</UseAlternateColorScheme>
    <NumberOfItemsInGrids>3</NumberOfItemsInGrids>
  </General>
  <DoNotShowMessages>
    <HideNoResultsMessage>False</HideNoResultsMessage>
    <HideCloseConfirmation>True</HideCloseConfirmation>
  </DoNotShowMessages>
</UserPreferences>

We’d also need a corresponding LoadXML method to read and parse out the XML and set the internal values.  That seems pretty good and it prevents us from having to modify the database table every time we add a new preference.

But, in a more critical and more annoying way, we have to modify the SaveXML and LoadXML methods every time we add a new preference.  Not only that, but we have to compensate for previously-saved versions of the XML, saved before new preferences were added, otherwise we’ll get errors when we try to read nodes that don’t exist.  This is a path of misery and spaghetti.

Don’t be discouraged.  There is an easy way.  In fact, it’s so easy it’s near unbelievable.  You add this code once and never change it.  Add all the preferences/properties you want and the code works with whatever is available.  It uses the XMLSerializer to do all the work.

First, all the classes need to be marked as <Serializable()>.  Then we create a method to instantiate the UserPreferences class:

    Shared Function GetInstance(ByVal userID As Integer) As UserPreferences
        Dim up As UserPreferences
        Dim ser As XmlSerializer
        Dim dt As DataTable
        Dim dr As DataRow

        dt = SqlHelper.ExecuteDataset(CONN_STRING, CommandType.Text, _
            "select preferences from userpreferences where userid=" & userID).Tables(0)

        If dt.Rows.Count <> 0 Then
            dr = dt.Rows(0)
            ser = New XmlSerializer(GetType(UserPreferences))
            up = TryCast(ser.Deserialize(New IO.StringReader(CStr(dr("Preferences")))), UserPreferences)
            up._UserID = userID

        Else
            up = New UserPreferences(userID)

        End If

        dt.Dispose()

        Return up

    End Function

Then we add a public method to save the class:

    Public Sub Save()
        Dim ser As XmlSerializer
        Dim xmlData As IO.StringWriter
        Dim parms As New Generic.List(Of SqlClient.SqlParameter)

        ser = New XmlSerializer(GetType(UserPreferences))
        xmlData = New IO.StringWriter
        ser.Serialize(xmlData, Me)

        With parms
            .Add(New SqlClient.SqlParameter("@UserID", _UserID))
            .Add(New SqlClient.SqlParameter("@Preferences", xmlData.GetStringBuilder.ToString))
        End With

        SqlHelper.ExecuteNonQuery(CONN_STRING, CommandType.Text, _
            "insert userpreferences(userid,preferences) values(@UserID,@Preferences)", parms.ToArray)

    End Sub

Seriously, that’s it.  Three lines to turn an object into XML.  Two lines to create an object from XML.  Missing and extraneous properties get skipped with no errors, which lets you change the classes whenever and however you need.  And the structure of the XML is the same as shown previously, with child classes/properties as nested elements.

In a test app, you can load, change, and save preferences:

image20

With the preferences in an object, the UI code is extremely simple – one of the great benefits to using objects:

    Dim _prefs As UserPreferences

    Private Sub cmdLoad_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdLoad.Click
        _prefs = UserPreferences.GetInstance(CInt(txtUserID.Text))

        With chkGeneralPreferences
            .SetItemChecked(0, _prefs.General.ShowSplashScreen)
            .SetItemChecked(1, _prefs.General.UseAlternateColorScheme)
        End With

        txtNumberOfItemsInGrid.Value = _prefs.General.NumberOfItemsInGrids

        With chkDoNotShowMessages
            .SetItemChecked(0, _prefs.DoNotShowMessages.HideNoResultsMessage)
            .SetItemChecked(1, _prefs.DoNotShowMessages.HideCloseConfirmation)
        End With

    End Sub

    Private Sub cmdSave_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdSave.Click
        With _prefs.General
            .ShowSplashScreen = chkGeneralPreferences.GetItemCheckState(0) = CheckState.Checked
            .UseAlternateColorScheme = chkGeneralPreferences.GetItemCheckState(1) = CheckState.Checked
        End With

        _prefs.General.NumberOfItemsInGrids = CInt(txtNumberOfItemsInGrid.Value)

        With _prefs.DoNotShowMessages
            .HideNoResultsMessage = chkDoNotShowMessages.GetItemCheckState(0) = CheckState.Checked
            .HideCloseConfirmation = chkDoNotShowMessages.GetItemCheckState(1) = CheckState.Checked
        End With

        _prefs.Save()

    End Sub

Save That Email. As an Email.

Applications typically send a lot of emails.  At least, they should, since it’s a good, archive-able method for communication and confirmation.  Archive-able for the receiver, sure, but what about the sender – the application?  You have a couple of options: you can parse out all the fields of the email and stick them in a database or you can CC or BCC the email to a mailbox for archival.  What if you want to store the actual email that was sent?  You want the actual EML file.

Wouldn’t it be nice if the MailMessage object had a .SaveAs method?  It doesn’t.  Well then, wouldn’t it be nice if the SmtpClient object had a .SaveTo property?  It does, kind of.  And by using those properties, we can capture the actual EML file that is typically sent to a SMTP server for delivery.  Once we have that EML file data, we can save it to a file or to a database or wherever.

Quite simply, you need to set two properties on the SmtpClient object: DeliveryMethod and PickupDirectoryLocation.  This tells the SmtpClient to write the EML to a specified folder and the mail server will monitor that folder and pull it from there.  This is the code:

    Private Function GetEmailBytes(ByVal eml As Mail.MailMessage) As Byte()
        Dim smtp As Mail.SmtpClient
        Dim customFolderName As String
        Dim fileBytes() As Byte

        customFolderName = IO.Path.Combine(My.Computer.FileSystem.SpecialDirectories.Temp, Guid.NewGuid.ToString)

        IO.Directory.CreateDirectory(customFolderName)

        smtp = New Mail.SmtpClient
        With smtp
            .Host = "localhost"
            .DeliveryMethod = Mail.SmtpDeliveryMethod.SpecifiedPickupDirectory
            .PickupDirectoryLocation = customFolderName
            .Send(eml)
            .Dispose()
        End With

        fileBytes = IO.File.ReadAllBytes(New IO.DirectoryInfo(customFolderName).GetFiles.First.FullName)

        IO.Directory.Delete(customFolderName, True)

        Return fileBytes

    End Function

To explain the extra legwork involving directories, the SmtpClient writes the EML file with a GUID as a filename.  This prevents emails from overwriting each other.  In a multi-user environment though, how could we know which file was just written so we read the right file? So to be sure what we’re reading is our email, we create a unique folder to write the EML to and we know there will be only one file in there to read.

The GetEmailBytes method just returns the bytes of an EML file.  That’s the most flexible way to work with the data.  If you want to save that to another place with another name, just use IO.File.WriteAllBytes, like so:

    Private Sub SaveMessage()
        Dim eml As Mail.MailMessage
        Dim bytes() As Byte

        eml = New Mail.MailMessage
        With eml
            .To.Add(New Mail.MailAddress("anyone@home.com"))
            .From = New Mail.MailAddress("nobody@home.com")
            .Subject = "test save"
            .Body = "this is the body"
        End With

        bytes = GetEmailBytes(eml)

        IO.File.WriteAllBytes(IO.Path.Combine(My.Computer.FileSystem.SpecialDirectories.Desktop, "output.eml"), bytes)

        eml.Dispose()

    End Sub

Uneventful

After reading the post about events and AddHandler and tracking references and hanging objects, you might be a little concerned about using events at all, maybe because of the added code or the housekeeping.  If so, you can consider another way of doing event-type behavior using Delegates.

Delegates can be used many different ways.  This particular way might be a little different than what you’ve previously seen.  The general concept is to keep a variable holding all the delegates to be called for an event.  This variable (as a delegate itself) can be invoked at any time, and more importantly, can be cleared at any time.

Reusing some old code from the previous post, we still have our HangingObject class and we now have this method to build the objects and call their internal method:

    Private Sub UsingDelegate()
        Dim o As HangingObject

        HelloList = [Delegate].RemoveAll(HelloList, HelloList)
        
        For i As Integer = 1 To 5
            o = New HangingObject
            HelloList = [Delegate].Combine(HelloList, New SayHelloDelegate(AddressOf o.SayHello))

        Next

        HelloList.DynamicInvoke(Nothing)

    End Sub

And at the form level, we have the delegate sub definition and the delegate variable holding all the calls.

    Public Delegate Sub SayHelloDelegate()
    Dim HelloList As [Delegate] = Nothing

So what’s different?  We’ve changed the event declaration to a Delegate Sub declaration; we’ve added a variable to hold all the event subscriptions; instead of using AddHandler, we use Delegate.Combine to register the event subscription; and instead of RaiseEvent, we use DynamicInvoke.  There’s not much more or less, but everything is different.  The list of subscribers is accessible now through the variable HelloList, which is a huge benefit.

The two interesting parts of this code are the shared methods on the Delegate class: Combine and RemoveAll.  The delegate itself (HelloList) contains a list of invocation targets, similar to subscribers of an event.  The Combine method merges the specific SayHelloDelegate invocation list with the generic HelloList invocation list, resulting in one list of all the targets.  Calling the DynamicInvoke method performs a .Invoke on all the delegates in the invocation list.  Simple and magical.

However, because HelloList is declared at the form level, it persists between calls and can suffer the same issues as the AddHandler method.  The nice thing is that you can clear the invocation list by using RemoveAll, or you can just set the variable to Nothing.  If HelloList was defined within the method instead of the form, it would be cleared at the end of the method, unlike the AddHandler method, where the event is declared at the form level.

It’s good to know a lot of different ways to do something, just in case.  Another tool in the coding toolbox.

ByVal, ByRef, Structure, Class, ugh.

In another post I referred to this topic as a phenomena.  That’s pretty over-dramatic.  But there are some important rules and guidelines for types in .NET and until you know why things happen as they do, it seems like voodoo.  So here’s an all-in-one example to clear it all up.  But, be warned that it’s going to be very confusing until it “clicks”.

A brief prelude:  .NET has two variable types, value types and reference types.  A value type is stored on the stack, is generally small, and contains its actual data.  A reference type is stored on the heap, has an unknown or variable size, and contains pointers to the actual data.  .NET also has two definitions that utilize these types: Structures and Classes.  Structures are value types and Classes are reference types.

Method calls support two parameter keywords: ByVal and ByRef.  ByVal passes the parameter data to the method by value, in other words, it sends the values to the method.  ByRef sends the parameter data to the method by reference, it sends a reference to the parameter data.  The thing that is most misunderstood is that if the parameter type is a reference type (a class) and it is sent ByVal, the value is a copy of the reference.  When the parameter type is a value type (a structure) and is sent ByVal, the value is a copy of the value – a real copy.  Here’s a quick real-world summary of method/parameter behavior:

  • A class passed ByVal – the method works on the original (kind of)
  • A structure passed ByVal – the method works on a copy
  • A class passed ByRef – the method works on the original
  • A structure passed ByRef – the method works on the original

If you’re still following, you might notice some vagueness in the most common method call type: classes passed ByVal.  This will be explained after a demo of these different combinations.  So for demonstration, we create a Class and a Structure with a public field:

Public Class NameClass
    Public Name As String

    Public Sub ChangeName()
        Name &= " (changed)"
    End Sub

End Class

Public Structure NameStructure
    Public Name As String

    Public Sub ChangeName()
        Name &= " (changed)"
    End Sub

End Structure

A method is in each to modify the internal state.  This will prove whether we are working on a copy or the original.  Next, we make a class that uses these objects and modifies them in private methods with parameters passed in various combinations:

Public Class PersonClass
    Public Name1 As New NameClass
    Public Name2 As New NameStructure

    Public Sub UpdateNames()
        Name1.Name = "Class Name"
        Name2.Name = "Structure Name"

        Debug.WriteLine("Name1 before ByVal: " & Name1.Name)
        UpdateClassByVal(Name1)
        Debug.WriteLine("Name1 after ByVal: " & Name1.Name)

        Debug.WriteLine("Name2 before ByVal: " & Name2.Name)
        UpdateStructureByVal(Name2)
        Debug.WriteLine("Name2 after ByVal: " & Name2.Name)

        Debug.WriteLine("Name1 before ByRef: " & Name1.Name)
        UpdateClassByRef(Name1)
        Debug.WriteLine("Name1 after ByRef: " & Name1.Name)

        Debug.WriteLine("Name2 before ByRef: " & Name2.Name)
        UpdateStructureByRef(Name2)
        Debug.WriteLine("Name2 after ByRef: " & Name2.Name)

    End Sub

    Private Sub UpdateClassByVal(ByVal item As NameClass)
        item.ChangeName()
    End Sub

    Private Sub UpdateStructureByVal(ByVal item As NameStructure)
        item.ChangeName()
    End Sub

    Private Sub UpdateClassByRef(ByRef item As NameClass)
        item.ChangeName()
    End Sub

    Private Sub UpdateStructureByRef(ByRef item As NameStructure)
        item.ChangeName()
    End Sub

End Class

So, after instantiating this class and calling UpdateNames, we get the following results:

Name1 before ByVal: Class Name

Name1 after ByVal: Class Name (changed)

Name2 before ByVal: Structure Name

Name2 after ByVal: Structure Name

Name1 before ByRef: Class Name (changed)

Name1 after ByRef: Class Name (changed) (changed)

Name2 before ByRef: Structure Name

Name2 after ByRef: Structure Name (changed)

In the same order as the bullet list above, we can see that Name2 (the structure) passed ByVal did not change, showing that the method was working on a copy.  Everything else remained changed after leaving the method call, showing they were working on the original.

Now to add confusion and clarity to the ambiguity of passing classes ByVal…

When you pass a class to a method ByVal, you are sending a copy of the reference.  Everything that is inside that class is a reference as well, so when you change a property, it’s still changing the same property in the original – they share the same reference.  This essentially is like working on the original.  However, you cannot change the class itself.  What?

Here’s another bit of code to add to PersonClass to illustrate:

    Public Sub UpdateObjects()
        Name1.Name = "Class Name"

        Debug.WriteLine("Name1 before ByVal: " & Name1.Name)
        UpdateClassObjectByVal(Name1)
        Debug.WriteLine("Name1 after ByVal: " & Name1.Name)

        Debug.WriteLine("Name1 before ByRef: " & Name1.Name)
        UpdateClassObjectByRef(Name1)
        Debug.WriteLine("Name1 after ByRef: " & Name1.Name)

    End Sub

    Private Sub UpdateClassObjectByVal(ByVal item As NameClass)
        item = New NameClass
        item.Name = "Replaced Class"
    End Sub

    Private Sub UpdateClassObjectByRef(ByRef item As NameClass)
        item = New NameClass
        item.Name = "Replaced Class"

    End Sub

What this code does is try to reassign the value of Name1 to a new instance.  When you call UpdateObjects, you will see you can’t change the instance of Name1 when the parameter is passed ByVal, but you can when it is passed ByRef.

Name1 before ByVal: Class Name

Name1 after ByVal: Class Name

Name1 before ByRef: Class Name

Name1 after ByRef: Replaced Class

Again, because ByVal passes a copy of the reference where ByRef passes the actual reference.  If you reassign the value when it is passed ByVal, you’re only reassigning to a copy, which has no effect on the original.

In real-world usage, using ByVal with classes is going to work for you 99% of the time, but you need to understand why and how things work to handle that odd 1% of cases and avoid crazy workarounds.

Why Won’t You Go Away?!

All .NET developers should know that .NET is pretty reference-happy.  There’s an interesting phenomena with Structures and Classes that I can illustrate later, but the issue I wanted to point out here involves references and garbage collection.  As we know, .NET manages object cleanup through the garbage collector when there are no more references to the object.  This is kind of nice because you can let objects just fall out of scope and the GC will take care of everything.  That is, if nothing else has a reference to those objects.

One thing to pay special attention to is event handlers.  These create references and can keep objects alive MUCH longer than you want; maybe for the running life of the application.  As a potential scenario, you have a form with a form-level event.  That form has a method that creates some objects that listen for that event.  The method does its stuff and finishes.  You call the method again and suddenly you have twice as many responses to the event.  The objects you set up from the first run still exist and listen for that event.

Here’s some demo code to illustrate this.

The class that responds to the event:

Public Class HangingObject

    Public Sub SayHello()
        MsgBox("Hello from " & Me.GetHashCode.ToString)
    End Sub

End Class

The method that creates the objects and raises the event:

    Private Sub ShowHanging()
        Dim o As HangingObject

        For i As Integer = 1 To 5
            o = New HangingObject
            AddHandler Me.SayHelloEvent, AddressOf o.SayHello

        Next

        RaiseEvent SayHelloEvent()

    End Sub

And the form-level event:

Public Event SayHelloEvent()

So if you call ShowHanging, you get 5 Messageboxes.  If you call it again, you get 10, and so on.

The reason for this is the AddHandler statement.  AddHandler creates a reference to the instance of the object “o” and stores it with the form-level event SayHelloEvent.  When do these references get removed?  When the form is disposed.  If that form is the main form of the application, that will be when the application ends.

Can you get around this?  Maybe by implementing Dispose and disposing the objects?

Public Class HangingDisposableObject
    Implements IDisposable

    Public Sub SayHello()
        MsgBox("Hello from " & Me.GetHashCode.ToString)
    End Sub

    Private disposedValue As Boolean
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
            End If

        End If
        Me.disposedValue = True
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

End Class

    Private Sub ShowHanging()
        Dim o As HangingDisposableObject

        For i As Integer = 1 To 5
            o = New HangingDisposableObject
            AddHandler Me.SayHelloEvent, AddressOf o.SayHello
            o.Dispose()
        Next

        RaiseEvent SayHelloEvent()
    End Sub

You’d be surprised.  You still get 5 Messageboxes even though all five were Disposed inside the loop.  Ah, but the garbage collector hasn’t run yet.  So let’s force it to do a collection.

    Private Sub ShowHanging()
        Dim o As HangingDisposableObject

        For i As Integer = 1 To 5
            o = New HangingDisposableObject
            AddHandler Me.SayHelloEvent, AddressOf o.SayHello
            o.Dispose()
        Next

        GC.Collect()
        RaiseEvent SayHelloEvent()
    End Sub

Still surprised?  You shouldn’t be.  The GC won’t collect (Finalize) the objects because there is still a reference to that object (the event handler).  So how do you manage this?  You have to call RemoveHandler to remove the reference.  And that means you have to keep your own references to the objects until you’re done with them and you do the cleanup yourself.

    Private Sub ShowHanging()
        Dim oCollection As New Generic.List(Of HangingObject)
        Dim o As HangingObject

        For i As Integer = 1 To 5
            o = New HangingObject
            AddHandler Me.SayHelloEvent, AddressOf o.SayHello
            oCollection.Add(o)

        Next

        RaiseEvent SayHelloEvent()

        For Each o In oCollection
            RemoveHandler Me.SayHelloEvent, AddressOf o.SayHello
        Next

    End Sub

So IDispose is not the answer.  Keeping track of your objects and the references they hold is the answer.  And that should be the obvious answer anyway.

Finding Solutions In Problems

I’m always looking for novel ways to accomplish something, even it’s totally inefficient… This sounds familiar.

Interestingly, the solution I was experimenting with in the last post is closely related to a problem I took up for resolution today.  In the inefficiency of the code, I found a very clever and unique way of splitting a string on a delimiter like a comma.  Now in this particular case, there was a much more efficient way to accomplish the end result, but I can appreciate the creativity in this query.

This is the code that was focused on:

declare @SortExpression VARCHAR(100) = 'state,zip',@SortOrder VARCHAR(10) = 'ASC'

SELECT STUFF(
(SELECT ', ' + Parsed 
FROM (
    SELECT SUBSTRING(@SortExpression + ',', Number,
        CHARINDEX(',', @SortExpression + ',',Number) - Number) + ' ' + @SortOrder AS Parsed
    FROM Common.dbo.Numbers
    WHERE Number <= LEN(@SortExpression)
    AND SUBSTRING(',' + @SortExpression, Number, 1) = ','
    ) t2
FOR XML PATH(''),TYPE).value('.[1]', 'varchar(MAX)'),1,2,'')

Whenever you see three SELECT keywords and XML together to result in a single value, something seems out of place.  This code takes two strings, “state,zip” and “ASC” and turns it into a single string “state ASC, zip ASC”.  That’s a lot of code for just that.

The interesting part of that query is in the middle, where the query accesses the a database with a table called Numbers which just has a list of numbers from 1 to 10000.  In another post, I created a list of numbers, the exact same thing, in memory using UNPIVOT and a CTE.  I could’ve done the exact same thing to fix this, but the eventual solution I came up with does the same with no recordsets at all to deal with.

Drilling into the inner query,

SELECT SUBSTRING(@SortExpression + ',', Number,
    CHARINDEX(',', @SortExpression + ',',Number) - Number) + ' ' + @SortOrder AS Parsed
FROM Common.dbo.Numbers
WHERE Number <= LEN(@SortExpression)
AND SUBSTRING(',' + @SortExpression, Number, 1) = ','

the thing I find so clever about this query, despite its inefficiency, is the approach it takes.  It uses a sliding window of characters, breaking on the comma, and looking for a trailing comma (that was manually added as a terminator).  When you see the results without the SUBSTRING criteria in the WHERE clause, it looks like:

Parsed

state ASC
state ASC
tate ASC
ate ASC
te ASC
e ASC
ASC
zip ASC
ip ASC
p ASC

And with the SUBSTRING check, the only rows returned are the ones that have a trailing comma.  Code like this makes your head hurt.  In fact, it’s so complex, you almost have to accept it at face value, thinking it’s just so complex, you’d better not touch it.  That is when you isolate and experiment, get the same results, prove it out with different values, and improve it.

So what’s the best replacement I could come up with?

select replace(@SortExpression,',',' ' +@Sortorder + ',') +
case when @SortExpression<>'' then ' ' + @Sortorder else '' end

One complete statement.  Whee!