views:

215

answers:

2

I've noticed some seemingly weird issues in Visual Studio 2008 (.NET 3.5) and also in Visual Studio 2010 Beta 2 (.NET 4.0). These issues may have existed in prior versions as well. Maybe they are not an issue, but either way, I would like to see if there is are logical explanations for these before I submit a report on Microsoft Connect.

The Setup (in VB, C# results differ and are included later in the post):

Public Class SomeClass
    Public Property SomeProperty() As String
        Get
            Return String.Empty
        End Get
        Set(ByVal value As String)
        End Set
    End Property
End Class

Public Class SomeOtherClass
    Public Sub New()
        Dim sc As New SomeClass()
        Me.SomeFunction(sc.SomeProperty)
    End Sub

    ''' <summary>The param as Object fn()</summary> '''
    Public Sub SomeFunction(ByVal param As Object)
    End Sub

    ''' <summary>The param as T fn()</summary> '''
    Public Sub SomeFunction(Of T)(ByRef param As T)
    End Sub
End Class

In this sutation, the Me.SomeFunction(sc.SomeProperty) call, from the point of view of IntelliSense, looks like this:
alt text
and, not surprisingly, this is also what is called at runtime.

So, I guess the first question I have is, why was the ByRef templated overload version of the function chosen over the ByVal Object overload version of the function? My guess is that the compiler and IntelliSense simply favor the templated versions over non-templated versions. At runtime, it is in fact the ByRef templated version of the function that is called. (This is not the defect, this is simply a personal want-to-know question.)

Now, make a slight change to the SomeProperty property such that the setter is now private:

Public Property SomeProperty() As String
    Get
        Return String.Empty
    End Get
    Private Set(ByVal value As String)
    End Set
End Property

As soon as you do this, the following happens to the Me.SomeFunction(sc.SomeProperty) line:
alt text

In this case, IntelliSense is suggesting that the ByVal Object overload version of the function is being called, however the error message is the 'Set' accessor of property 'SomeProperty' is not accessible indicating that the compiler is still expecting to call the ByRef templated version. So, this is my second question. Why is Intellisense claiming one thing while the VB compiler is clearly trying something else? This seems broken to me. Or am I missing something?

If instead of having a private setter on SomeProperty but instead the property was simply marked ReadOnly and the setter part removed, then the ByRef templated version of the function is shown in IntelliSense and is called at runtime (with no runtime errors). So this leads me to my third question, why is the VB compiler treating the input to ByRef params different for properties that are ReadOnly vs not-ReadOnly, but have an out-of-scope setter in VB As far as SomeFunction(Of T)(...) is concerned, in its current scope, that property should be as if it were ReadOnly and I would expect it to be callable just as if the property was in fact ReadOnly. But instead it produces a build error.

In correlation with question three, doing the exact same setup (with the Private setter), C# has the result I was expecting. alt text
Here, you can see IntelliSense is claiming that the SomeFunction(Object) overload of the function is being called and there is no build error. At runtime the SomeFunction(Object) version is in fact called. So, why isn't the same SomeFunction(Object) version being called in the VB.NET situation? Why does VB.NET still think the SomeFunction(Of T)(ByRef T) version need to be called? It looks like IntelliSense is nailing it correctly in both C# and VB.NET, the C# compiler is doing the right thing, but the VB.NET compiler is still convinced that it should be calling the ByRef templated version. To me it seems that the C# compiler is picking one overload, while the VB.NET compiler is picking a different overload, in the exact same situation. Am I wrong?

+4  A: 

Regarding your third question:

Why is the VB compiler treating the input to ByRef params different for properties that are ReadOnly vs not-ReadOnly, but have an out-of-scope setter in VB

In CLR, it is not possible to pass a property as a ByRef. The VB.NET team, however, decided that this would be useful and implemented a workaround. Internally,

Me.SomeFunction(sc.SomeProperty)

is converted into either

Dim vbTemp = sc.SomeProperty
Me.SomeFunction(vbTemp)

which is called Don’t Copy Back ByRef and used, e.g., for Read-Only properies, or

Dim vbTemp = sc.SomeProperty
Me.SomeFunction(vbTemp)
sc.SomeProperty = vbTemp

which is called Copy Back ByRef and used for properties containing both a getter and setter. All these things (and a few more complicated cases) are explained in jaredpar's WebLog: The many cases of ByRef.

Now, you are saying that when the setter is out of scope, a Don’t Copy Back ByRef should be performed. However, that would lead to inconsistent behavior: SomeFunction(sc.SomeProperty) would update sc.SomeProperty when called inside of SomeClass, but the same line of code would silently not update the property when called outside (because the setter is out of scope).


About your first and second question:

Why was the ByRef templated overload version of the function chosen over the ByVal Object overload version of the function? My guess is that the compiler and IntelliSense simply favor the templated versions over non-templated versions.

They favor the templated versions over a version that would require a widening cast to Object. Overload resolution is described in detail in Chapter 11.8.1 of the Visual Basic Language Specification.

Why is Intellisense claiming one thing while the VB compiler is clearly trying something else?

Looks like a bug to me.

Heinzi
"The VB.NET team, however, decided that this would be useful and implemented a workaround." -- It's too bad their workaround either didn't first check for another valid-to-use overload before reverting to the workaround you described. Meaning mimic what C# does first, which is look for another valid overload first. That said, C# should also have followed suit with VB on this and not looked for another valid overload, but instead do the trick. Sucks that the same code produces different function calls based on the language (VB vs C#), all other things being equal.
ckittel
Well, it's not the same code: In VB, calling `Func(p)` means `Func(ByValOrByRef p)`, whereas in C#, `Func(p)` means `Func(ByVal p)` (as opposed to `Func(ref p)`, which means `Func(ByRef p)`). Thus, the VB statement has much more potential overloads available (those with ByVal and those with ByRef), whereas the C# ones are restricted to either ByVal (like in your call) or ByRef (if you had used `ref`).
Heinzi
@Heinzi Interesting. I did notice that forcing the param to be ByVal [via SomeFunction((sc.SomeProperty))],does allow it to compile and call the SomeFunction(Object) overload.
ckittel
A: 

If is entirely possible that they pick different versions - overload resolution is a complex area that is defined in great detail in spec documents. For example, you need to specify in what order to consider use of generics, use of implicit cases / conversions / boxing, etc.

Also - remember that in C# ref must be explicit, so the ref version isn't even a valid overload from the code. In VB it is implicit, and (via the generic version) can use an exact match without any conversions / casts / etc.

Confusingly, it can be that overload resolution comes before accessibility checks, so the private thing (by itself) isn't surprising.

I also don't think intellisense necessarily defines the version actually being invoked - just the overload*s* that are available.

Marc Gravell