Visual Basic 15 brings with it partial implementations of two important C# features: tuples and ref returns. Neither feature is “complete”, but they do offer enough work-arounds that VB applications can consume C# libraries that make use of these features.
Tuples
The ability to directly return multiple values from a single function call has long been a requested feature in VB. Although you can get the same result using ByRef parameters, the syntax is clumsy compared to what you see in functional programming languages.
If you aren’t familiar with the term, a “tuple” is just a set of related values. Since .NET 4.0, Visual Basic has had a standard Tuple class, but the user experience has been less than satisfactory. The values have to be manually unpacked from the Tuple using the unhelpful property names Item1, Item2, etc.
Seven years later, VB 15 finally adds syntax support for tuples. Or rather, the ValueTuple structure, which offers better performance than the heap-allocated Tuple object. Here is an example of a TryParse method written using the new style:
Try
Dim numericValue = Integer.Parse(s)
Return (True, numericValue)
Catch
Return (False, Nothing)
End Try
End Function
Dim result = TryParse(s)
If result.Item1 Then
WriteLine(result.Item2)
End If
In this example, the return type of TryParse is ValueTuple<Boolean, Integer>. This makes the function a bit cleaner as you never have to explicitly mention the ValueTuple type. But as you can see, the code that calls it isn’t any different. So let’s improve it a bit by renaming the fields on the return type:
Try
Dim numericValue = Integer.Parse(s)
Return (True, numericValue)
Catch
Return (False, Nothing)
End Try
End Function
Dim result = TryParse(s)
If result.Item1 Then
WriteLine(result.Item2)
End If
You can create new tuples with named fields using this syntax:
Dim kvPair = (Key := 5, Value := "Five")
Unfortunately, there isn’t a way to “unpack” a tuple in VB into multiple variables. So translating this C# code into VB requires one line per variable.
var (key, value) = kvPair;
(By)Ref Returns
Ref returns, known as ByRef returns in VB, are severely limited. You can consume C# functions that return a reference (i.e. managed pointer) to a field or array index, but you can’t create your own. Nor can you create local byRef variables.
What you can do is a rather complicated work-around. While again you cannot create local ByRef variables, you can create ByRef parameters. Consider this C# function signature and its VB equivalent:
public ref string FindNext(string startWithString, ref bool found)
ByRef Function FindNext(startWithString as string, ByRef found as Boolean) As String
To use this, Klaus Löffelmann tells us that we need a helper function:
Private Function VbByRefHelper(Of t)(ByRef byRefValue As t,
byRefSetter As Func(Of t, t)) As t
Dim orgValue = byRefValue
byRefValue = byRefSetter(byRefValue)
Return orgValue
End Function
You can then pass the result of the ref-returning function as a parameter to the helper function, along with what you want to do with it as an separate anonymous function.
Dim didFind As Boolean
'Version #2: With a simple generic helper-class:
aSentence = New NewInCS2017.Sentence("Adrian is going to marry Adriana, because Adrian loves Adriana.")
Do
VbByRefHelper(aSentence.FindNext("Adr", didFind),
Function(stringFound) As String
If stringFound = "Adrian" Then
stringFound = "Klaus"
Return stringFound
End If
Return stringFound
End Function)
Loop While didfind
For reference, this is what it would have looked like had VB fully implemented ref returns:
'THIS DOES NOT WORK IN VB!!!
Dim aSentence = New Sentence("Adrian is going to marry Adriana, because Adrian loves Adriana.")
Dim found = False
Do
' !!In C# we can declare a local variable as ref - in VB we cannot.!!
' This variable could take the result...
Dim ByRef foundString = aSentence.FindNext("Adr", found)
If foundString = "Adrian" Then
' but via the reference, so writing would be possible as well,
' and we had a neat find and replace!
foundString = "Klaus"
End If
Loop While found
So why not fully implement ref locals?
Well, basically because nothing uses them yet. There is nothing in the .NET API that currently requires their use and the feature may still end up being a dead end. This has happened before; almost no one uses unsafe blocks, even when working with native C libraries. The same can be said for stack-allocated arrays.
Anthony Green writes,
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#).
[…]
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 full explanation it the link above)
VB isn’t the only language to do this. The same concerns led C# to not include support for XML literals, a feature that a lot of ASP.NET MVC developers would have loved to have.