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.

Thursday, September 21, 2006

.NET Assembly Resolution

Thanks to .NET, DLL Hell is officially ended. But I at least still find myself in DLL Heck from time to time (thanks to Scott Adams for the term).

We still seem to have too many issues where something works on one machine but not on another (usually it works on the developer's machine but not when we install it on a director's laptop to show him that we haven't spent the last month just browsing the Internet).

Our app has a simple footprint: we bung everything, including third-party controls, into a bin folder. How can anything go wrong? The loader looks in the app folder for stuff first, doesn't it? WRONG!

We get away this simple-minded view most of the time because (and I know you'll tell me off for this) we aren't yet strongly naming our assemblies. In this case, the loader only looks in the app folder (and below).

But for strongly named assemblies, the loader looks in the GAC first. This makes sense. The loader is always looking for a specific version of an assembly; you control which assembly versions are loaded for your app via the version policy, not by shadowing shared DLLs with your own copies in the app bin folder.

Here are some links that explain all this unambiguously.

Assembly Versioning (from the MSDN Library)

Resolving Names to Locations (from .NET Common Language Runtime Components by Don Box & Chris Sells)

Useful diagram from the second link:

Bonus information from that book extract: if you put <developmentMode developerInstallation="true" /> in the configuration/runtime node of the machine.config file, the loader first looks in the path specified by the DEVPATH environment variable.

Friday, September 08, 2006

HTTP Partial Get - The Point

Got so into blogging the "how" in the last post that I forgot about the "why", although I hope that's fairly obvious.

The W3C puts it like this:

The partial GET method is intended to reduce unnecessary network usage

and this:

A server MAY ignore the Range header. However, HTTP/1.1 origin servers and intermediate caches ought to support byte ranges when possible, since Range supports efficient recovery from partially failed transfers, and supports efficient partial retrieval of large entities.

I.e.

  1. Keep the Internet clean (hear that, spammers?)
  2. Speed up client requests.

I'm sure I had a point, but it's gone. The thought of all the crap floating around in the ether has depressed me too much to think. I think I need to see Marvin to cheer me up.

BTW our web hosting company honours ranges so perhaps it's not all that bad.

HTTP Partial Get

Part of our application allows the user to drag/drop document links onto a window. When the link is a web page shortcut, I wanted to get the page title as the description for that link. The only way I could find to do this was to download the page using an HttpWebRequest object, and parse for the <title> tag.

This is OK except that I didn't want to download an entire page just to get a piece of information that, if present, will be somewhere near the top. Enter the partial get capability of HTTP 1.1. If you add a "range" header to the GET request, the server should return only the specified byte range from the document. Here is the code I used.

 

   PrivateFunction GetWebPageTitle(ByVal url As String) As String

 

       Dim request As HttpWebRequest

       Dim response As WebResponse

       Const bytesToGet As Integer = 1000

 

        request = DirectCast(WebRequest.Create(url), HttpWebRequest)

        request.AddRange(0, bytesToGet - 1)

        response = request.GetResponse

 

        ...

 

   EndFunction

 

However, the operative word here is should (i.e. it's not mandatory behaviour). In half-a-dozen web sites I tried (including microsoft.com), only one took any notice of my range request:

Some things to note:

  • The response code is 206 (Partial Content)
  • Content-Length = 1000
  • Content-Range = 0-999 (the first 1000 bytes) of a total of 126,444
  • Accept-Ranges: Bytes tells us the server recognizes the Range header in the GET request (which we now know, but we could have issued a HEAD request to find this out)
  • The well-behaved web site is w3.org (of course!)

(The screenshot is from Wireshark, formerly Ethereal.)

Tuesday, August 29, 2006

Windows Live Writer

If you can see this, I've successfully started using Windows Live Writer, and should hopefully start blogging more, now it's a bit easier.

Tuesday, May 16, 2006

Why the Fancy UI?

I recently subscribed to the Joy of Code blog which I am liking a lot, and this post struck a chord with me. The thrust of the article is that software vendors shouldn't waste time tarting up their interfaces: what we need is standard, functional interfaces. While I agree that these "flashy" UIs can be very frustrating, or even unusable, I enjoy seeing an app go beyond the ordinary and try something different. Arguably the most successful company at not following the "Microsoft way" is of course Microsoft:

Microsoft Money

To answer the rhetorical question, I guess development teams at virus scanner companies have pretty much done the hard stuff, so have time to work on differentiating themselves from the competitors. Still if you're just trying to get something done, a plain, simple, intuitive UI is what you need.

Friday, April 21, 2006

Development Philosophy

When I write code, I usually try to imagine all the conditions under which the code will run, what can fail, etc., and write code or add comments to handle or describe these conditions. I am sure I am not alone in this and that there are a number of formalized procedures that promote this.

I am wondering if this is becoming an "old fashioned" way to program.
The modern way seems to be to bash out code as fast as possible without thinking too much, and fix problems as they arise. I expect this is not entirely new, but rather is simply becoming more common, and perhaps even more accepted. In fact the whole agile thing seems to address how one can code in this fashion but still end up with good code.

I am suddenly suspicious that I am in danger of losing touch, and becoming an "old fogey" in coding terms. Not because I don't keep up with my reading (I do) but because deep down I am still holding on to old ideals and methods.

Still, they say that half of fixing a problem is recognizing you have it :-)

Sunday, March 12, 2006

Reflection Over Generic Types - Follow-Up

Venkat (who gave the DNR TV presentation) follows up with some more details.

Saturday, March 11, 2006

Reflection Over Generic Types - Bug?

Inspired by a episode #9 of DNR TV, I ran the following program to see the type info for a generic base class, where the type parameter is passed from the derived class, and was unpleasantly surprised by the result:


    Public Class Base(Of T)
    
End Class

    Public Class Derived(Of T)
        
Inherits Base(Of T)
    
End Class

    
Public Class Program

        
Shared Sub Main()
            DisplayInfo(
GetType(Base(Of Integer)), 0)
            DisplayInfo(
GetType(Derived(Of Integer)), 0)
        
End Sub

        Private Shared Sub DisplayInfo(ByVal type As Type, ByVal indent As Integer)
            WriteLine(type.Name, indent)
            WriteLine(type.FullName, indent)
            
If type.IsGenericType Then
                WriteLine("Yes, it's generic baby!", indent)
                
If Not type.IsGenericTypeDefinition Then
                    WriteLine("and here's its generic type def:", indent)
                    DisplayInfo(type.GetGenericTypeDefinition, indent + 1)
                
End If
            Else
                WriteLine("It's not generic, man!", indent)
            
End If
            If type.BaseType IsNot GetType(Object) Then
                WriteLine("and here's its base type:", indent)
                DisplayInfo(type.BaseType, indent + 1)
            
End If
        End Sub

        Private Shared Sub WriteLine(ByVal s As String, ByVal indent As Integer)
            Debug.Write(Space(indent * 4))
            Debug.WriteLine(s)
        
End Sub

    End Class


Here's the output:


 1    Base`1
 2    WindowsApplication1.Indent.Base`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
 3    Yes, it's generic baby!
 4    and here's its generic type def:
 5        Base`1
 6        WindowsApplication1.Indent.Base`1
 7        Yes, it's generic baby!
 8    Derived`1
 9    WindowsApplication1.Indent.Derived`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
10    Yes, it's generic baby!
11    and here's its generic type def:
12        Derived`1
13        WindowsApplication1.Indent.Derived`1
14        Yes, it's generic baby!
15        and here's its base type:
16            Base`1
17
18            Yes, it's generic baby!
19            and here's its generic type def:
20                Base`1
21                WindowsApplication1.Indent.Base`1
22                Yes, it's generic baby!
23    and here's its base type:
24        Base`1
25        WindowsApplication1.Indent.Base`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
26        Yes, it's generic baby!
27        and here's its generic type def:
28            Base`1
29            WindowsApplication1.Indent.Base`1
30            Yes, it's generic baby!


The problem is with the blank line on line 17. The base of the open generic class definition for Derived has no full name! Presumably line 17 should show the same as line 6. If you change Derived(Of T) to inherit from Base(Of Double) for example, all is fine.

Friday, February 17, 2006

Upgrade Fun - Moving to VS2005

Following on from my first post, we are now starting to upgrade for real and trying to get rid of those warnings. This is being hampered by the background compiler: every time I fix up a bit of code off it goes, hogging my CPU, until it has gone through the entire solution I guess. A search on the MSDN forums turns up this from MS guy Matthew Gertz:

"The background compiler cannot be turned off in Visual Basic .Net – it’s actually the thing that’s providing all of the Intellisense information, formatting information, and so on. I discuss this in some detail here. In that article are some tips to improve performance in larger-scale applications."

To minimize this problem, I am opening our projects one by one (bottom-up in the reference tree) instead of all at once in our monster solution. In fact, I think our development is going to have to continue this way until the background compiler's performance (or at least its perceived performance) is greatly improved.

Using an instance variable to access a shared member (Error ID: BC42025)

I would like to turn this warning off rather than fix it, because using a short
variable name in place of a sometimes quite long class name can reduce clutter and
make the code easier to read. However one of the ways we do this is we declare an
instance of an enum type (nested in another class) to provide short-cut access to
the enum literals, which results in an "unused variable" warning which I definitely
don't want to turn off.

Fortunately, initializing the enum variable removed the "unused" warning, at least in this compiler version. This is fortunate because the warning was also occurring when accessing members of the DialogResult enumeration on a form: Form also has a DialogResult property which hides the enum so you end up having to fully qualify it (or partially qualify it if you import a parent namespace, e.g. System.Windows).

Getting out of bad VB6 habits, and other details

The new compiler is much more fussy (a Good Thing) at pointing out poor form, including:

  • Not explictly initializing reference variables if they are going to be used before an assignment;

  • Not explictly returning Nothing from a function (a variant of this is having return statements inside a Select - you're then forced to define an "else" clause, which is another Good Thing);

  • Leaving unused variable declarations lying around.

Mind you, the explicit initializer thing is a bit of a pain when you're only going to initialize it to Nothing anyway. I've gotten too used to seeing lack of an initializer as an invisible Nothing or 0 (note the compiler doesn't fuss about default initialization of value types - why the inconsistency?) Also, the parser doesn't recognize patterns such as this, and reports that ref is used before being initialized:


Dim isFirstTime As Boolean = True
Dim ref As SomeClass
For i As Integer = 0 To 10
If isFirstTime Then
ref = SomeMethod()
isFirstTime = False
Else
'use ref
End If
Next


Another annoying variant of this is "Variable 'XXX' is passed by reference before it has been assigned a value. A null reference exception could result at runtime." This is annoying when you are passing by reference because the method is going to initialize the variable. This is a weakness of ByRef in VB: it means "in/out" and we have no keyword to specify just "out".

Proper XML commenting - Hooray!

We had had a half-hearted attempt to add these in using the VBCommenter add-in in Visual Studio 2003, but stopped when we found Intellisense wasn't picking up our descriptions. We've now got to fix syntax errors (invalid whitespace), mismatched param elements etc. in the existing comments.

I am getting a strange error though: "XML documentation parse error: "Whitespace is not allowed at this location. XML comment will be ignored." This is being reported inside the summary element. I.e. I can cut all the lines between <summary> and </summary> and it is fine, but when I paste the original text back (which contains no XML or anything obviously funky), the error springs back.

D'oh - the summary text contained an ampersand! So it seems like it is a real XML document fragment.

Inappropriate use of 'Overloads' keyword in a module

This surprised me. The help link merely says that modules aren't classes and therefore can't use MustInherit etc., blah, blah. Nothing about why Overloads is invalid on Module members. You can use it on Shared class members, so what's the problem? Amanda Silver explains:

We decided to give this warning because the application of the Overloads keyword only makes a difference to the semantics of the application when the method is overloaded across an inheritance chain. As the Overloads is superfluous in the context of a Module (and possibly misleading) we decided to emit a warning. Note, however, that the warning can be turned off by going to the project properties and selecting the "Disable all warnings" check box.

I love the last sentence!

I need to revise the purpose of Overloads and Shadows to fully understand this, but I would have thought that if you get a warning for Module members, you should get one for Shared class members too. Stay tuned.

Name '_blah' is not CLS-compliant

In a number of places, we have protected fields which, because they are fields, begin with an underscore. Because protected members are exposed outside the assembly and therefore available to other assemblies, they should be CLS-compliant (which means they can't begin with an underscore). The easiest way round this, since we don't need to interop with other .NET languages, is to mark the entire assembly as not CLS compliant in Assembly.vb.

Keeping Your Friends Close

I don't like the "Friend" access modifier in VB.NET ("internal" in C#) because it provides a very sloppy kind of access control, allowing as it does any method in the entire assembly to access the Friend type or member.

In C++ you could (I suppost "can" would be better, but I don't think I'll be using C++ very much in the future) explicitly specify who your friends are, to the class or even method (or top-level function) level, which means you keep control over the interfaces to your class.

From a library writer's perspective (when looking at the library as a whole at least), Friend is somewhat useful because it lets you separate "us" from "them", but when you're writing in-house code and trying to design coherent public and proptected interfaces, Friends can become your enemies.

Especially since we are encouraged to write "big" assemblies to reduce load times and so can't use the assembly as a fine-grained partitioning mechanism for tightly coupled classes.

What would be nice is a namespace-like mechanism to allow you to group classes together into friendships, so that only classes in the same friendship group have access to each other's Friend members.

Namespaces themselves can't be used of course, because anyone can add additional types to a namespace.

But you could say that Friend members where only accessible from type in the same assembly and namespace. Or, better, allow the Friend keyword to be used on a namespace declaration to mean "Friends within this namespace aren't accessible outside it" (obviously the "same assembly" restriction would still hold). The good thing with this approach is that if you don't use it you get the current behaviour.

What do you think Mr. Vick?

Update

Having proudly e-mailed Paul Vick my suggestion, I blushingly realized that what I should have done is enter it via the "Make a Suggestion" link on the MSDN product feedback page. When I started to do that I found several other suggestions along the same lines (i.e. reintroducing C++ style friend specifiers), but none I think as simple as mine, although I have conveniently ignored how these friend namespaces should be managed across multiple source files in the same assembly - i.e. should you be made to specify "friend" for all of them, or should doing it once automatically apply to all; perhaps it is analagous to partial classes. Also I didn't think of the work that would be needed in the CLR; after all, namespaces are just syntactic sugar, right? Yeah, but friend/internal isn't! Sometimes I wish I'd just keep my (e-)mouth shut.

Update #2

Paul Vick replied on 26th Feb:

Hey, Ian, I apologize for taking so long to write back! More granular Friend control is definitely something that's been requested in a number of different ways and it's something we'll definitely look at as we plan our next release. It's definitely a matter of trying to balance the need for control versus the need to keep the concept count down as much as possible, always a delicate trade off.
Thanks for making the suggestion and thanks for using VB!
Paul

Tuesday, February 14, 2006

Synchronize Class View in VS2005

Wanted to give this some more Google-juice (though I don't think even the Google spider reads my blog) because the help docs lie:


http://blogs.msdn.com/ansonh/archive/2005/12/09/502020.aspx

A Quote for New Bloggers

When you start a blog it is with high hopes that your fantastic insight and wit will garner a steadily growing readership, with occasional rapid rises when some blog-ebrity links to you from his blog.

Sadly this is not generally the case, and you either give up in defeat, or relegate your blog to a personal diary. Alternatively you can image that there really are hundreds, nay thousands, of appreciative readers who are just too shy to leave comments.

With a few notable exceptions (see, a good blogger would put some links in here) most of the popular blogs out there are popular because they are written by well-known speakers or authors (and I'm being parochial here, I've no idea what happens in blogs I don't read).

And here's why (and finally the point of this post):

http://www.quotationspage.com/quote/1984.html

P.S. if anyone is reading this blog, please excuse posts mysteriously appearing "in the past"; I'm pulling stuff together that I've already posted elsewhere, or queued to post, and I want to keep the original dates on stuff. This is a personal diary after all!

Monday, October 31, 2005

Dear Mr. Vick

I wanted to lend some support to a great idea I heard from Paul Vick on .NET Rocks!

"Heard you on .NET Rocks! mention the possibility of doing away with VB's line continuation character - presumably making it more C-like in its treatment of whitespace. I think this would be GREAT!

"I have come to VB from a long history of C and C++ and (especially with VB.NET) the only think that has really niggled is VB's line-oriented parser.

"I expect you'll have to pull off some pretty neat tricks with context-sensitive parsing but please, please please do it!"

Friday, October 28, 2005

SQL Surprise

Got tripped up by this little bit of SQL (which I naively bashed out in Query Analyzer):

select * from Addresses where AddrID in
(select AddrID from BankAccounts where BankID=234255)

The BankAccounts table includes a foreign key to Addresses, giving the address of the branch at which the account is held (there is no intermediate BankBranches table). So I was expecting this query to give me one row from Addresses (or perhaps none). In fact, it gave me the whole damn table.

The problem is that the BankAccounts table doesn't have an AddrID column; it's called BankAddrID. So why didn't the query give an error? I'm no SQL expert but basically it's because the outer select is in scope (which allows correlated subqueries to work) so the query I wrote was effectively:

select AddrID from Addresses
cross join BankAccounts
where BankID=234255

D'oh!

Thursday, September 15, 2005

Philosophical Development

Nice rant from Roy Osherove. The esoteric work is necessary up front to make the whole thing hang together properly, and you should be able to drill down into it if you find that useful or interesting, but it shouldn't pollute the mainstream information. What we need there are more problem-focussed discussions and less BS!

Wednesday, August 03, 2005

Combo Box Caching

Got tripped up the other day by an elementary piece of behaviour that I expect everybody
knows about except me (and the rest of my team). What do you think you will see
when you drop down the combo box in the following app?


Public Class Form1

Protected Overrides Sub OnLoad(ByVal e As EventArgs)

MyBase.OnLoad(e)

Dim c As New C("Marco")

ComboBox1.Items.Add(c)

c.Value = "Polo"

End Sub

End Class


Public Class C

Public Sub New(ByVal val As String)

Value = val

End Sub

Public Overrides Function
ToString() As String

Return
Value

End Function

Public
Value As String

End Class

Well the title of this post gives it away; of course you see "Marco". The combo box caches the ToString results when the items are added. I have yet to find a way to tell it to update its cached strings, without removing and readding items.


I love the fact that you add objects to combos and other lists, but this behaviour just shows it up as something of a hack: we might as well still be storing strings and putting the object reference in ItemData.


This happens in VS.NET 2003 and 2005. My friends tell me Java has a much more sophisticated model with paging and everything. Come on Microsoft, time to catch up!

Monday, July 25, 2005

What's on HDTV?

Could this be the definitive blog on HDTV? I haven't read it all yet, but it looks like it deserves to be read all the way through from the beginning.

Sunday, July 24, 2005

VSTS Part 2

Well, carrying on with the course after the last rant, we come to a useful diagram that shows how the three editions and the "Foundation Server" fit together.

Visual Studio Team System diagram