Why VB2017 only supports consuming ref returning methods

Hi VBers,

Last week Klaus wrote an amazing post detailing a number of improvements made to the Visual Basic IDE and language in Visual Studio 2017 (and he even forgot one, stay tuned for awesome). Regarding the new ref-return feature Jonathan Allen inquired as to why the design was so different from the one in C#. It’s not uncommon for considerations in one language to be different in the other or for the styles of the languages to yield different design decisions, even from the same people. But it’s a great question so I thought I’d write up a longer explanation for the VB design.

To put it bluntly, the capabilities around ref-returning method consumption in VB2017 are designed to hedge VB’s bets against an uncertain future rather than to bring that feature into the mainstream (which it isn’t, even in C#).

The #1 scenario for ref returns is array slices–a collection type being discussed for the .NET framework which will let you ‘slice’ off a subset of an array and hand that slice to some other code. We tried an earlier attempt of this with the ArraySegment(Of T) type but it has several drawbacks. Most importantly, that its performance isn’t at all comparable to accessing the array element directly. One reason for that is because it requires a lot of copying. So, prior to VS2017 we’d need to write the slice type like this.

structure Slice(of T)
    private readonly _Array as T()
    private readonly _Offset as integer
    sub new(array as T(), offset as integer)
        _Array = array
        _Offset = offset
    end sub
    default property Item(index as integer) as T
        get
            return _Array(_Offset + index)
        end get
        set(value as T)
            _Array(_Offset + index) = value
        end set
    end property
end structure

 
The lines in bold are the problem. The getter copies the element at that offsets and returns it. So whatever the size of the element code must pay the cost of copying it. Then the consumer has to copy that value to a local variable. And on the other side if someone wants to write to the array they have to copy the value to call the setter and the setter then copies it again to set the element of the array.

Also, because of this difference you can’t modify a member of the element directly if T is a value type. So you couldn’t say (imagine this slice is from an array of points):

‘ Doesn’t work.
mySlice(3).X += 1

 
The reason for that is because the value returned from mySlice(3) is a copy, not a reference to that element. So modifying it doesn’t really mean anything. So today you’d have to write:

Dim temp = mySlice(3)
temp.X += 1
mySlice(3) = temp

 
So, in addition to paying for all the copying I mentioned earlier the indexing operation happens twice; once to get the value and once to set it. With a true array this doesn’t happen—there is a single index and the value is modified in place without being copied. These difference may seem small, but in certain high performance applications (*cough* games) the extra overhead is devastating. Enter ref returns. Now a slice default property (or indexer in C#) can be written which directly returns a reference to the underlying array index. That eliminates all of the overhead I just talked about. The index happens only once, and no copying takes place unless you store the value of that element in a variable or set that element to a different value. Other than computing the offset that performance is virtually identical to accessing the underlying array directly.

Now, we haven’t specifically added a slice type to the .NET Framework but it’s something we’re thinking over adding one day. There are other design issues that need to be resolved but this was the very first thing needed to unblock them.

If you look at C# this release this one feature pulled in another feature, namely ref locals. There are also considerations around ref locals and decisions like ref re-assignment (making a local ‘point’ to another location) and whether ref locals are readonly by default and how to specify refness in all of the places a variable can be introduced, differentiating regular assignment from ref assignment, etc. that would all need to be solved in VB as well to enable the same production capabilities. That’s a lot of plumbing.

And in VB it would be even more plumbing because VB has extra functionality around ByRef such as passing a property ByRef. VB compiler development lead Jared Parsons has a great post detailing the many cases of ByRef in VB that explains this. It doesn’t really make any sense to return a property ByRef and handling ByRef in a late-bound context and so the language would work differently for ByRef returns that ByRef parameters (in addition to all the considerations in C#) which is confusing (and Jared would have to go update that post with yet more cases). That’s a lot of confusion, syntax, and conceptual overhead to add to VB because C# added a feature that might be used to write one collection type someday.

Instead we took a different approach. In short, we implemented just enough of the consumption scenario to allow you to do exactly with a Slice (or ref returning thing) what you can do with an array and nothing more than that. You can assign directly to the array element when you index it but you can’t assign a reference from an array element to a local and assign back into the array later, you can modify the value at that index in place, and you can pass that element ByRef but you can’t return it ByRef. This means no new syntax and VB is protected if we add slice between VS2017 release and the next version of the language and slice becomes the collection type to end all collection types and a million APIs pop up everywhere overnight all returning things ByRef. And if that highly improbable nightmare scenario never happens the language bears no scars from it. And that’s why the design is the way it is.

You can read the original design notes from last year here which briefly say what I’ve said above. But that’s the whole story. Nothing sinister or because VB was thought about at the last minute. It was a tactical decision made to target precisely the scenario for which the ref-return feature was added to protect VB users from possibly important consumption scenarios appearing between releases while protecting VB from considerable feature creep of dubious value to its users. Feel free to leave any feedback below or to me directly on Twitter (@ThatVBGuy).

Regards,

Anthony D. Green, Program Manager, Visual Basic