Tuesday, October 31, 2006

You're Shadowing My Stupidity Overload

When I wrote my Overloads vs Shadows post, I was obviously still in the grips of my confusion. One of those times when you think you're being clever, but in fact you're not quite clever enough.

Having looked into what the compiler does (and admittedly not much more at the spec), and calmed down a bit, I offer this attempt at clarification.

  • Within the same "declaration context" (Class, Interface or Module), you can use the same name more than once, as long as at most one of the uses is a typey thing (class, enum, event/delegate), and all the others (which are methods or properties) have different signatures. No keywords are necessary nor can they influence the compiler's behaviour.
  • When considering inherited members with the same name as your own members:
    • If you're overriding a method/property, that's fine. We all know what that's about.
    • Otherwise, by default, the member in your class Shadows all inherited members of the same name. None of the inherited members are accessible except by casting your object reference to the base type.
    • However, if you add the Overloads keyword to the member in your derived class, the member only makes inaccessible any base members that have the same signature.

So the confusion stems (for me at least) from being brought up to believe that Overloads means "there are other methods/properties in this class with the same name". Curse you, section 4.1.1!

I promise never to use Overloads this way again.

Catch When What?

Joe Duffy's excellent blog has just taught me something (in guideline 16 of this article) that I didn't know about structured exception handling in VB.NET, and which is not explained in the language spec (see 10.10.1).

What do you think this code should do?

    1 

    2 Module Module1

    3 

    4     Private _value As Integer

    5 

    6     Sub Main()

    7         _value = 42

    8         Try

    9             Trouble()

   10         Catch ex As Exception When _value = 7

   11             MsgBox("Oh, that's all right then.")

   12         Catch ex As Exception

   13             MsgBox("The non-7 value is: " & _value.ToString)

   14         End Try

   15     End Sub

   16 

   17     Sub Trouble()

   18         Try

   19             Throw New Exception

   20         Finally

   21             _value = 7

   22         End Try

   23     End Sub

   24 

   25 End Module

   26 

It prints "The non-7 value is: 7" of course.

Without a great deal of research (i.e. none) it seems the two passes mentioned by Joe are:

  1. find the target of the exception (i.e. the closest matching catch block on the stack);
  2. transfer control to that catch block via each finally block between it and the throw point.

Therefore no finally-block code will have executed when the When clause is executed, so invariants could still be broken.

No big deal (since exception filters should be kept simple), but worth knowing.

Tuesday, October 24, 2006

Overloads Overloads

Finally got around to digging into something that's been bugging me since February, re the warning message that can be issued by VB.NET in VS2005: "Inappropriate use of 'Overloads' keyword in a module".

As related in that post, the Microsoft explanation (from the lovely Amanda Silver) is: "the Overloads keyword only makes a difference to the semantics of the application when the method is overloaded across an inheritance chain". But we all know that an overload is when two methods (properties etc.) share the same name but have different signatures, right? It even says so in the VB language spec (section 4.1.1): "The only way to declare identically named entities of the same kind in a declaration space is through overloading."

Ah, but that's the concept called overloading, which is obviously different from the overloading keyword. A few pages further on in the spec (section 4.3.3), all is made clear: "Shadowing by name is specified using the Shadows keyword. ... Shadowing by name and signature is specified using the Overloads keyword.". I.e. "overloads" is actually overloaded in Visual Basic! And those C# snobs call us stupid ;-)

Joking aside, the point is that Overloads and Shadows are telling the compiler about the relationship between a name in the current class and the same name in base classes, not about the relationship between same-named members within one class. They are saying "this member is not related to members of the same name that I happen to inherit; specifically, it does not override any of them". So actually, these keywords are only necessary when the base member is overridable (i.e. virtual).

As mentioned, the difference between the two keywords is that Overloads prevents overriding of a specific signature while Shadows prevents overriding of anything with the same name; the following code is illegal:

    1 

    2 Public Class Class1

    3 

    4     Public Overridable Sub F(ByVal x As Integer)

    5     End Sub

    6 

    7     Public Overridable Sub F(ByVal s As String)

    8     End Sub

    9 

   10 End Class

   11 

   12 Public Class Class2

   13     Inherits Class1

   14 

   15     Public Shadows Sub F(ByVal y As Integer)

   16     End Sub

   17 

   18     Public Overrides Sub F(ByVal s As String)

   19         MyBase.F(s)

   20     End Sub

   21 

   22 End Class

   23 

It generates the error "sub 'F' cannot be declared 'Overrides' because it does not override a sub in a base class." on line 18. Replace Shadows with Overloads on line 15 and it works just fine.

Here's the equivalent in C#, which only has a single keyword for this duty, i.e. new:

    1 

    2 public class Class1

    3 {

    4     public virtual void F(int x) { }

    5     public virtual void F(string s) { }

    6 }

    7 

    8 public class Class2 : Class1

    9 {

   10     public new void F(int y) { }

   11     public override void F(string s) { }

   12 }

   13 

This compiles fine, leading us to deduce that new is equivalent to Overloads. However in the book on which I cut my .NET teeth (Table 7-4), Jeffrey Richter cites Shadows as the VB equivalent of new.

Hmm, I wonder... Did Overloads start off meaning "I meant to give these methods the same name" but morph into its current meaning when Microsoft realized Shadows was a bit too blunt-edged?


Update 31/10/2006

I was obviously still confused when I wrote the above post, so I've attempted to straighten it out here.

Thursday, October 05, 2006

WithEvents Addendum

(Follows on from yesterday's post.)

Because WithEvents fields are wrapped in a property, passing them by reference is should be a problem, but it magically seems to work! This is an example of VB.NET continuing in its fine tradition of "protecting" the developer from messy details.

Reflector reveals that if you write this:

Friend WithEvents Button1 As System.Windows.Forms.Button

 

Private Sub X()

    Y(Button1)

End Sub

 

Private Sub Y(ByRef a As Button)

End Sub

the VB compiler generates code for X() like this:

Private Sub X()

    Dim button1 As Button = Me.Button1

    Me.Y(button1)

    Me.Button1 = button1

End Sub

Wednesday, October 04, 2006

Suspending WithEvents Handlers

Sometimes you want to call methods on a WinForms control (for example) but not have it raise events, e.g. to prevent recursive calls. For handlers you've added yourself with AddHandler, the answer is obvious: remove the handler and re-add it when you've done your thing. However, in VB you've usually got automatically generated event handlers hooked up to WithEvents fields with the Handles keyword. What do you do about these? Is it OK to call Remove/AddHandler in the usual way?

(BTW I think the alternative technique of setting an "ignore events" flag is to be discouraged because it has to be tested in all of the handlers; by using Add/RemoveHandler you're keeping the code close to the point that requires it.)

The WithEvents section in the Language Spec is quite revealing on this point.

Basically, if you declare a WithEvents variable called x, what you actually get is a private variable called _x and a property called x that looks like this:

Friend Overridable Property x() As TextBox

    Get

        Return _x

    End Get

    Set(ByVal value As TextBox)

        If _x IsNot Nothing Then

            'Remove handlers from _x

        End If

        _x = value

        If _x IsNot Nothing Then

            'Add handlers to _x

        End If

    End Set

End Property

The list of handlers to add and remove is generated from all the methods that have a "Handles" clause for an event of this instance. If a WithEvents field has no handlers, the wrapper property is still generated but its setter simply sets the value of the hidden field.

Knowing this, you can temporarily suspend all event handling (other than via handlers you explicitly added yourself) with code like this:

Dim temp As TextBox = x

x = Nothing

temp.AppendText()   'Anything that would normally raise an event

x = temp

Caveat: the code between setting x to Nothing and restoring it can't call any methods that need to access x; also, this code should be wrapped in a Try/Finally to make sure the field is always reset to its original value.