Mark Gilbert's Blog

Science and technology, served light and fluffy.

Mom! He keeps touching me! Class Properties, Breakpoints, and the VS Debugger

The Setup

I use Session and Cache on a fairly regular basis in my web applications.  Instead of accessing Cache(“FirstName”) or Session(“FirstName”) directly from code, I’ll create strongly-typed properties that wrap those:

Public Property FirstName() As String
    Get
        If (IsNothing(Session("FirstName"))) Then Return ""
        Return Session("FirstName").ToString
    End Get
    Set(ByVal value As String)
        If (IsNothing(value)) Then value = ""
        Session("FirstName") = value
    End Set
End Property

Being strongly-typed, I don’t have to do explicit casts everywhere to get it into the type that I want.  I let the property do that for me.  The above manipulates a single String object, but in some cases, I’d like to load up a value from a database and put it into session or cache for later use (so I don’t have to keep running back to the database every time something uses this property).  That leads me to doing something like this:

Public Property FirstName() As String
    Get
        Dim MyPerson As Person
        Dim MyFirstName As String

        If (IsNothing(Session("FirstName"))) Then
            MyPerson = New Person
            MyFirstName = MyPerson.FirstName
            Session("FirstName") = MyFirstName
        Else
            MyFirstName = Session("FirstName")
        End If
        Return MyFirstName
    End Get
    Set(ByVal value As String)
        If (IsNothing(value)) Then value = ""
        Session("FirstName") = value
    End Set
End Property

In this example, if the FirstName value doesn’t exist in session already, I create a Person object which loads it from the database, store MyPerson.FirstName in session, and then return it.  The next time through, the value will already be in session so I don’t have to make the relatively expensive call to the database to look it up.

The Confusion

On a recent project, I wanted to verify that my strongly-typed-property-wrapping-Session-object code was working correctly.  I had two major pathways to test – first access and subsequent access.  On first access, I expected the item to not exist in Session yet and my property GETer code would put it in Session.  On the second and subsequent passes through, I expected the item to exist in Session.

My particular property was part of a class called SessionWrapper that was purely intended to expose items in Session.  I typically declare these properties to be “Public Shared” so I can just use them in code without needing to instantiate a class – just like how I would access the Session collection.  Next, I created a page with a property that wrapped one of the SessionWrapper properties.  So, a property within a property.

I set a breakpoint at the beginning of the GETer on the page property, and let it rip.  The execution stopped at the breakpoint and I stepped through.  It bypassed the “If IsNothing()“ check and went right into the Else.  The item was already in Session.  Did I miss something here?  This was the first pass – nothing should have been in session already.

The Utter Bewilderment

So, I stopped the debugger.  I did a search through my solution to make doubly-sure that this was the only place in code that was doing anything with Session(“FirstName”).  It was.

I ran a check through the rest of the solution looking for where else I might have been accessing the SessionWrapper property before my page was.  Nothing else was referencing it.

I ran my test again to see if it was just a fluke.  Nope – no flukes here, but something fishy is definitely happening.

The FirstName was already in Session by the time my breakpoint hit.  Now, don’t get me wrong.  The value being stored was what I expected.  I was just not expecting to see it already – I wanted to see it get loaded up.

I found a brick wall.

So, rapidly nearing desperation I set a breakpoint in the Application_Start event in Global.asax.  When that breakpoint hit, I looked at Session(“FirstName”).  It was Nothing.  Ok, so I’ve established that neither the application nor the web server are prescient.

We’re making progress.

Next I added a second breakpoint, this time to the beginning of the SessionWrapper property I was invoking.  I ran the app.  Our first stop is the Page property breakpoint.  I stepped into the call to the SessionWrapper property, and watched it blow right past the “If IsNothing()” check.  The Session object already had its value.

And I found the brick wall again.

As a colleague of mine, Doug, and I were troubleshooting this, we happened to notice that the Autos window for the Page was showing the SessionWrapper property as being populated already – even though I hadn’t stepped through the first breakpoint yet.  Interesting.

The Theory

Doug and I thought what might have been happening was that the VS debugger needs to invoke the SessionWrapper property in order to show the value in the Autos window when we paused execution in the Page.  However, it ignored any breakpoints along the way.

The Experiment

To test this, we removed the Page breakpoint, but kept the SessionWrapper breakpoint in place.  Perhaps if the debugger has no reason anymore to pause execution on the Page, it won’t try to run ahead and populate the SessionWrapper property.

And that’s exactly what happened.  The breakpoint on the SessionWrapper paused execution and when I stepped through this time the Session object was initially nothing, was populated, and then returned.  When I refreshed the page, the breakpoint on the SessionWrapper paused execution again, but this time the Session object had its value.  This is exactly what I had expected to see.

Oh look – there’s a door in the brick wall after all.

The Wrap Up

I knew that several things in the VS debugger would touch properties – Autos window, Locals window, any watches you have set, the Quickwatch popup, even just hovering over an element will evaluate a property.  This has led me to be extra careful in what I put into my property GETers.  These things are call far more often than you think, so doing something intensive or complex can lead to very odd problems.  However, this was the first time I had seen breakpoints skipped for code that was clearly being executed.  That was something newly unexpected.

I think I understand why the debugger needs to do it, though.  Take the QuickWatch window as an example.  If you tried to invoke that for an object in one code file, and it touched properties with breakpoints set in a completely different code file, it would have to do the following to honor those breakpoints:

  1. Start to open the QuickWatch window
  2. Jump you over to the other code file so you could see the breakpoints hit
  3. Wait for you to step through the breakpoints
  4. Return you to the original code file
  5. Reopen the QuickWatch window
  6. Lather, rinse, and repeat for each property with breakpoints

A very jarring developer experience, not to mention the logic needed to keep track of where you were and where you wanted to be.

So, the lesson here is take care with your GETers, and keep in mind that the debugger is doing more than you think behind the scenes.

Advertisements

April 1, 2010 - Posted by | Visual Studio/.NET

Sorry, the comment form is closed at this time.

%d bloggers like this: