Friday, November 24, 2006

Forwarding IEnumerable(Of T)

Often we have a class that implements IEnumerable by returning the enumerator from an embedded list or array:

    1 

    2 Public Class Class1

    3     Implements IEnumerable

    4 

    5     Private Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator

    6         Return _items.GetEnumerator

    7     End Function

    8 

    9     Private _items() As Integer

   10 

   11 End Class

   12 

When I tried to do the same thing with a generic class, I had a problem:

    1 

    2 Public Class Class2(Of X As New)

    3     Implements IEnumerable(Of X)

    4 

    5     Private Function GetEnumerator() As IEnumerator(Of X) Implements IEnumerable(Of X).GetEnumerator

    6         Return DirectCast(_items.GetEnumerator, IEnumerator(Of X))  'Throws InvalidCastException

    7     End Function

    8 

    9     Private Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator

   10         Return _items.GetEnumerator

   11     End Function

   12 

   13     Private _items() As X

   14 

   15 End Class

   16 

Line 6 throws an exception with the message "Unable to cast object of type 'SZArrayEnumerator' to type 'System.Collections.Generic.IEnumerator`1[System.Int32]'."

The answer is simple, but had me scratching my head for a while:

    6 Return DirectCast(_items, IEnumerable(Of X)).GetEnumerator

Wednesday, November 08, 2006

Numbers Divisible by their Reverse

Here is my solution Jon Galloway's puzzle (my guess was "tens of numbers"):

    1 Module Module1

    2 

    3     Sub Main()

    4 

    5         Dim testValue As Integer = 21

    6         Const maxTestValue As Integer = 1000000

    7         Dim count As Integer = 0

    8 

    9         Do

   10 

   11             If testValue Mod 10 <> 0 Then

   12                 Dim testString As String = testValue.ToString

   13                 Dim reverseString As String = Reverse(testString)

   14                 If reverseString < testString Then

   15                     Dim reverseValue As Integer = CInt(reverseString)

   16                     If testValue Mod reverseValue = 0 Then

   17                         Console.WriteLine(String.Format("{0} / {1} = {2}", testValue, reverseValue, testValue / reverseValue))

   18                         count += 1

   19                     End If

   20                 End If

   21             End If

   22 

   23             testValue += 1

   24 

   25         Loop Until testValue > maxTestValue

   26 

   27         Console.WriteLine(String.Format("{0} numbers <= {1} are non-trivially divisible by their reverse.", count, maxTestValue))

   28         Console.ReadLine()

   29 

   30     End Sub

   31 

   32     Private Function Reverse(ByVal value As String) As String

   33         Dim chars() As Char = value.ToCharArray

   34         Array.Reverse(chars)

   35         Return New String(chars)

   36     End Function

   37 

   38 End Module

The .NET limitation is that the String class has no Reverse method!

This gives the following rather interesting result:

It is easily seen that (only) numbers of the format "879*12" and "989*01" (using regex * = 0-or-more occurrences) are solutions to this problem.

I have found a rather elegant proof of this but unfortunately it is too large to fit in the margin ;-)

I wonder how this generalizes for bases other than 10.

Update 9/11/06:

Jon's solution post shows I should have taken the sequence a bit further before jumping to conclusions:

The pattern now looks like: (879*12)+ or (989*01)+

Reminds me of this old puzzle:

If you have n points on the circumference of a circle, and join them all together with straight lines, how many regions are outlined? (More than two lines may not cross at the same point.)

1 point => 1 region
2 points => 2 regions
3 points => 4 regions
4 points => 8 regions

n points => 2^(n-1) regions, right?

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.

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!