Type-Inference Enhancements

Type inference is great! You get the clean productivity of a dynamic programming language with the benefits of static typing. VB came out of the gate swinging back in 2008 with its type-inference but there’s yet more that can be done!

Flow-Sensitive Typing

The marquee feature is flow-sensitive typing, which I’ve been chasing (off and on) for 7 years now. It’s gone through several different names in that time: Option Infer Turbo, Smart Variable Typing, The Crazy Feature, Smart Casting (what Kotlin calls it), “That thing TypeScript does where…”, Smart Type Testing, and finally Flow-Sensitive Typing. I’m going to keep using that name because for 7 years I casually searched the internet for a consistent term to describe it across languages and didn’t find one until about a month ago so now I’m going to abuse it so that others may find it.

I was initially very hesitant to propose this feature to the design team. In fact, initially whenever a VB user would (always cautiously and in private for some reason, like they dare not hope) say to me “You know what I really want? I wish that if I test the type of a variable and it succeeds, I wish the variable acted as if it had that type inside the If”. And when this happened I’d say, “Oh yeah, but there are problems with that… and what we could do is declare a new variable with that type… pattern matching… easier”. And one day I got to writing out a long reply (presumably to someone online) explaining all the technical challenges and on one side of my brain I was enumerating the problems, and on the other side I was hacking out “well, actually…” solutions until finally I convinced myself that it was at least possible to do it.

So, I went to the VB LDM with my “crazy” idea. I let them know up front that I knew it was “crazy” but that I just wanted them to suspend their disbelief long enough to mull over some possibilities. And I didn’t exit the meeting with a resounding “let’s do it” but I did get some thoughtful head nods that it wasn’t quite as un-implementable as they thought it would be. And after the meeting, I was chatting to Mads Torgersen said to me in our 1:1, “You know, I think you’re underselling the feature and yourself. It’s not crazy at all, this is just one of those problems in programming and some languages solve it using pattern matching and some solve it this way” so I stopped calling it crazy and kept iterating on the design. Thanks, Mads!

Fortunately for you, you get the skip over the considerable time I’ve put into how it can actually be implemented in the Roslyn VB compiler without violating certain invariants and architectural truths I’d rather not disturb. You just get to just focus on the developer-facing implications, which are considerable. Let me walk you through the history

As I said, it all started from a very simple (to make) request: If I test the type of a variable and it succeeds, I want the variable to behave as if it had that type from then on.

As the saying goes, easier said than done.

If TypeOf animal Is Duck Then
    animal.Quack()
End If

Oh, I see now that I’m editing this that I should mention this isn’t Duck-typing, this is a compile-time Duck, I just picked that species by chance.

And, of course, the all time top requested VB feature, to be able to Select Case on the type of a value

Select Case TypeOf animal
    Case Mammal
        animal.SecreteMilk()
        
    Case Bird
        animal.LayEggs()
        
    Case Fish
        animal.Swim()
        
    Case Insect
        animal.Creep()
        
    Case Else
        Throw New NotImplementedException()
        
End Select

Which of course, works with short-circuiting operators like AndAlso and If

If TypeOf animal Is Bird AndAlso animal.WingspanInMeters > 2 Then
    ' Big Bird
End If

Let holders = If(TypeOf quadruped Is Bird,
                 quadruped.Hindlimbs, 
                 quadruped.Forelimbs)

And this progressive refinement of types works across subsequent checks too

If TypeOf animal Is Vertebrate Then
    animal.FlexBackbone()
    
    If TypeOf animal Is Reptile Then
        animal.ShedScales()
        
        If TypeOf animal Is Snake Then
            animal.Coil()
        End If
    End If
End If

This is particularly useful when using old COM style versioned (IVsProject, IVsProject2, IVsProject3) without having to maintain separate variables for each version of the interface.

But it’s not limited to automatic downcasting along the inheritance chain. With interfaces you can have variables of intersection (“And”) types

If TypeOf animal Is Mammal AndAlso TypeOf animal Is ICanFly Then
    ' Members of both types are available.
    animal.SecreteMilk()
    animal.Fly()
End If

Intersection types are not new to VB. Previously, such types were only possible in generic types and methods which used complex constraints

Sub M(Of TAnimal As {Mammal, ICanFly})(animal As TAnimal)
    animal.SecreteMilk()
    animal.Fly()
End Sub

 So, it’s a very natural extension to support them here.

And that brings us to a huge spoiler for a later section. The current design for ModVB is that “Nullable Reference Types” are implemented as a special case of this flow-sensitive typing (as if T were a subtype of T?, such that t IsNot Null is synonymous with TypeOf t Is T)

Let animal As Animal? = SomeFunction()

' (Actual error text subject to change)
' Error: `Move` is not a member of `Animal?`.
' -OR-
' Error: Cannot call member `Animal.Move` from nullable value.
animal.Move()

If animal IsNot Null Then
    ' No error.
    animal.Move()
End If

The further specifics of nullable reference types will be discussed in that section but keep in mind when reviewing this section that much of the infrastructure is shared between them.

Now, 20 years ago, I was 19 and excited about the upcoming VB.NET 2005. One of the new additions to the language was the now infamous IsNot operator. And I thought it would make so much sense if this new operator worked with TypeOf so I filed a feature request on… Connect? Whatever it was called then, and the then VB Language PM Amanda Silver politely replied back “Good idea, but it’s too late in the product cycle, we’ll consider it for next release”. It did not appear in the next release. Or the one after that. Or, technically, the one after that. But, fortunately in 2010 I had gotten a job at Microsoft working on the VB compiler and I was soon assigned to the Roslyn rewrite and we were redoing all the features again in managed code and I begged the then dev lead to let me implement one of the smaller features, just for fun, ya know, nothing up my sleeve. “Um, how about VB TypeOf?” and he said, “OK, sure”, and assigned me VB GetType. And then I clarified that I meant VB TypeOf not the VB equivalent of C# typeof, ya know, like C# is but definitely not VB Is and he fixed it and finally almost a decade of waiting was about the pay off.

I’d like to take the credit for being sneaky here, but we had a whole code review process, everybody could see what I was doing: I had to change the parse tree and update the IDE. The real credit goes to the reviewers for letting me get away with adding a new feature in the middle of the rewrite despite our strict policy against adding new features at that time. So thanks to them for not raining on my very old parade. Fast forward to Visual Studio 2015 and after 11 years, TypeOf ... IsNot ... finally shipped. The moral of the story is not “Never give up!” but instead that since I added it to the language, I’m now responsible for it until the end of time (but Paul Vick is also responsible because he added IsNot to begin with when I was young and impressionable).

Which brings us to negative uses

' Works with guard statements (Return, Exit, Continue, Throw).
If TypeOf animal IsNot Mammal Then Return
    
animal.ShedHair()

OrElse

' Monotremes are egg-laying mammals, including the Platypus and the Echidna.
If TypeOf animal IsNot Mammal OrElse animal.IsMonotreme Then
    mayLayEggs = True
End If

Loops too

Let animal As Animal = GetDolphin()

Do Until TypeOf animal Is ILandDwelling
    ' Or maybe I should have said:
    ' Do While TypeOf animal IsNot ILandDwelling?
    animal = animal.GetImmediateAncestor()
Loop

animal.Walk()

Select Case

Select Case TypeOf dinosaur
    Case IsNot Bird
        
        dinosaur.GoExtinct()
        
    Case Else
        ' This metaphor is straining.
        If dinosaur.IsDodo Then
            dinosaur.GoExtinct()
        Else
            ' Expensive O(n) lookup on Wikipedia.
            
        End If
        
End Select

And in queries

Let usStateBearsByHibernationPeriod =
    From
        state In usStates
    Let
        mammal = state.StateMammal
    Where
        TypeOf mammal Is Bear AndAlso Not mammal.IsExtinct
    Order By
        mammal.HibernationPeriod Descending

So far, so good! And this is, with a few caveats, is where the design of this feature stabilized up until about 3 months ago, when consideration of another seemingly unrelated feature much, much farther down my backlog triggered a stray thought.

' In vanilla VB, type inference requires the 2nd & 3rd operands
' to convert to the type of one or the other or type inference
' fails, resulting in type 'Object'.
Dim animal = If(someCondition, New Cat, New Dog)

Under Option Strict On this is an error and under the Option Strict (Custom) settings I use this is also an error but even if it weren’t, this is still suboptimal to me.

In ModVB if there is no target-type (new in ModVB) for the If operator, and no common type between the 2nd & 3rd operands, rather than skipping straight to Object at the bottom of the hierarchy, the compiler will instead search for the most derived common base class or interface

' In ModVB, 'animal' would have type 'Carnivora',  which is the taxonomical
' order common to cat-like and dog-like placental mammals, but that's not
' actually useful because not all carnivorans are carnivores, e.g. bears
' are often omnivores except for the Giant Panda which mostly eats plants.
' So, even though it's inferred that type, the only members I can use are
' defined on 'Mammal' in this example, which is still more than I could
' use if the type were 'Object', so... 
Let animal = If(someCondition, New Cat, New Dog)
animal.SecreteMilk()

But then I got to thinking, if we later added union types to the language, wouldn’t the expectation be that the type of animal were Cat OR Dog? Surely, I should give some thought to that now just in case. Besides, the code “knows” that it’s a Cat or a Dog, isn’t it wasteful to throw that information away?

And this then became a design principle for the feature, to not throw away type information the compiler has without a good reason. Now let’s take a quick break to talk notation for this brand new world.

  • Let’s start with a type: A
  • Next, as mentioned above, VB already has syntax for intersection types in generic constraints: {A, B}
  • Now we need a syntax for union types: A Or B or {A Or B}
  • {A} is the same as A, so the curly braces can be used for either conjunction or grouping depending on your needs. I tend to always wrap these special types in curly braces because that’s most likely how they would appear in a program due to operator precedence.
  • Other mathematical properties of association and simplification also apply like {A Or {B Or C}} is the same as {A Or B Or C}
  • Now add another more explicit syntax for intersection types for completion sake: A And B or {A And B}. This means exactly the same as {A, B} and vice versa

Now we need some conventions. Let’s go back to our very first example

1: Let animal As Animal = GetAnimal()
2: 
3: If TypeOf animal Is Duck Then
4:     animal.Quack()
5:
6: End If
7:

On line 2, the IDE will tell me that the type of animal is Animal, as declared. What should it say on line 4? You might expect Duck but there are some implications to that decision. Certainly it can Quack like a Duck and be passed to Duck taking methods (still not duck-typing) but the interesting question is, what happens if I attempt to assign to animal on line 5? The variable was declared to accept any Animal, however, in the range of this If it is temporarily promoted to Duck so is re-assignment allowed? If so, must it be assigned only instances of Duck? if so and I attempt to assign an instance of Animal, is that reference automatically downcast to Duck?

Skipping ahead, the answers are yes, no, and no. It’s been a common enough experience in my career that I’m walking up or down tree-like data structures (controls on a form, nodes in a parse tree, entries in a file system, etc.) where I have code like this

Let item As BaseClass = firstItem
Do While SomeTest(item)
    item = GetParentOrChild(item)
Loop

You know, “Set focus to the first control, but if the first control is a GroupBox or Panel, set focus on its first control”, or “Walk up my containers until you hit a thing”, trust me, it happens, so it’s important that these variables retain the ability to be assigned and to be assigned any object of their declared type. And beyond that VB doesn’t yet have read-only local variables and this isn’t the time or place to add them.

So, how to denote this in a way that informs rather than confuses the developer. Here’s the convention I’ve come up with: When a variable’s type is temporary changed (promoted?), tooling and error messages will show its type as {DeclaredType, TemporaryType}. You can think of this as primary and secondary type, or write type and read type. Applying this to the example

1: Let animal As Animal = GetAnimal()
2: 
3: If TypeOf animal Is Duck Then
4:     animal.Quack()
5:
6: End If
7:

On line 4, the IDE will tell me that the type of `animal` is {Animal, Duck}. Which is true, it’s an Animal And it’s a Duck. This might seem a little redundant but the convention is that the first type in the list always tells you what’s legal to assign to the variable and any types that follow are what will be read out at this point in execution. So, animal can be assigned any Animal but is currently a Duck.

And naturally this all applies as you might expect to AndAlso

If TypeOf animal Is Mammal AndAlso TypeOf animal Is ICanFly Then
    ' Type of 'animal' is '{Animal, {Mammal And ICanFly}}'
End If

And OrElse

If TypeOf animal Is Bat OrElse TypeOf animal Is Bird Then
    ' Type of 'animal' is '{Animal, {Chordate And ICanFly}, {Bat Or Bird}}'
End If

But the cascade of design implications doesn’t stop here because now it becomes necessary to revisit the whole nullable types thing in light of these changes. Originally, I said that the way it worked was that If t IsNot Null Then was the same as If TypeOf t Is T Then and the type of the variable would automatically change from T? to T and there would be much rejoicing. But now the question is does this logic hold up if instead treating T? like a base type that has a derived type of T, one reasons about T? as a synonym for {T Or Null}?

The full implications of that question will be covered in future sections on “Null and Nothing” and “New Kinds of Types” but the short answer is yes for this feature but there are some additional decisions that need to be made if the desire is enforce that consistency, with regard to performance, etc. The end result is that conceptually and if only internally, there’s a new extremely limited type, the Null type. We have And types, Or types, the Null type, and T? is the same as {T Or Null}

Up until this point I’d only considered assignment as a thing that could restore a variable to its declared type: If you re-assigned an animal in a way that would break its temporary type you’d be limited to only using it as an Animal again. Part of that was thinking about implementation simplicity but then I get to thinking about this kind of scenario

Let animal As Animal
If choice = 1 Then
    animal = New Cat
ElseIf choice = 2 Then
    animal = New Dog
Else
    animal = New Fish
End If

What’s the type of animal after the End If? Just Animal? Surely, we’re not just going to throw away that information? C’mon! Look at that information. It’s so adorable… 🥺

And now that we have these ad-hoc union types we can say with confidence that the type of animal after the End If is: {Animal, {Cat Or Dog Or Fish}}

Or maybe even {Animal, Chordate, {Cat Or Dog Or Fish}} because you may need to access the DorsalNerveCord property for some reason.

The exact canonical display format for such types will be the subject of much rigorous discussion at a future date! What’s important to remember is that the first type (Animal) is what can be written to that variable. All other information is supplemental.

But, I hear you say, “What about this case?”

If TypeOf animal IsNot Fish Then
    fishtank.Add(animal)
End If

“Shouldn’t it be a compile-time error if you try to Add an animal that’s not a Fish to fishtank?” (Note: the alternative is to implicitly-convert animal to Fish and fail at runtime). “Sure, it’s not a common case but… THINK ABOUT THE INFORMATION! Doesn’t the compiler look kind of dumb letting you do that when the code clearly says it’s not a Fish?”

And I groan. And I sigh. Yep, got me there. So, it’s not the case that a variable can just sometimes gain types that it is, it can also gain types that it isn’t. That is to say, we now have And types, Or types, the Null type, and Not types. And the type of animal inside the above If block is: {Animal, Not Fish}

This leads to some fun display implications… would you rather see

{Animal, Not {Cat Or Dog Or Fish}}

Or

{Animal, Not Cat And Not Dog And Not Fish}

Or

{Animal, {Not Cat, Not Dog, Not Fish}}

How about

{Animal, {ICanFly And IEatInsects}, {Chordate Or Arthropod}, Not Bat}

That’s going to be fun to bike-shed on later but there is a really cool implication of this with nullable

1: Let animal As Animal? = TryGetPet()
2:
3: If animal Is Null Then
4:    
5: Else
6:    
7: End If

The type of animal on line 4 is {Animal?, Null}, and on line 6 it is {Animal?, Not Null} which for feature tracking when values will and won’t be null, I think is pretty cool!

It was at this point that my occasional perusing of Wikipedia finally revealed the name of this design to me: Flow-Sensitive Typing, which I am now obliged to use for future SEO purposes. It was… funny… to stumble upon the correct terminology only after all the hard work was done, but truthfully it likely wouldn’t have helped. Even this part (from a linked article):

(“you don’t say…”)

If I hadn’t worked through the individual scenarios myself in the context of VB I wouldn’t have appreciated them. So, seeing some prior art was more validating rather than humiliating and believe it or not the significant increase in scope over the original design somehow makes the implementation much simpler (and thus achievable).

For example, there’s this expectation that Select Case TypeOf implement a thing called subsumption checking. You can actually see an example of it in vanilla VB if you have on the “Duplicate or overlapping catch blocks” warning

Try
    ...
Catch ex As ArgumentNullException
    ...
Catch ex As ArgumentException
    ...
' Warning BC42029: 'Catch' block never reached,
' because 'ArgumentOutOfRangeException'
' inherits from 'ArgumentException'.
Catch ex As ArgumentOutOfRangeException
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

End Try

Originally the compiler would have reported a warning because an extra step during Case block binding would go back and check previous Case blocks for the same or parent types. Now, you’ll necessarily get an error because as a result of previous cases failing the type of the variable by that Case would be {Exception, Not ArgumentException} so the convertibility testing will fail, no extra work… maybe.

“But what about Gotos!?”

Well, I wrote a program using Goto statements which flowed from bottom to top changing a variable’s type each step (worst case scenario) and tried to follow both how the compiler’s implementation would track the changes and also how a calm and reasonable developer could understand the program’s behavior. I suspect that what resulted was some kind of miniature stroke. As such, while I’m not necessarily convinced that it’s impossible to implement a design that infers types in arbitrary execution orders (as opposed to top-to-bottom lexical order), I am convinced that doing so would cause grievous harm to the compiler, its implementers, and all who read such code. Therefore, the design is that if you use Goto statements in a way which allows execution to enter a region of code where a variable would otherwise have a certain type but bypass whatever flow-control which would guarantee that type AND then use that variable in a way which depends on it having that type, the compiler will raise an error (essentially a kind of definite assignment analysis)

1: Let animal As Animal = GetAnimal()
2:
3: If TypeOf animal Is Bat Then
4:
5:     animal.Fly() ' Illegal if code outside jumps to here.
6:    
7:     animal.Eat() ' Legal if code outside jumps to here.
8: End If
9:

That said, the design has re-stabilized and I don’t have any expectation to further expand it. But, feel free to do some research on “occurrence typing” and “effect type systems” and shoot me a mail if you think you can change my mind.

Oh, also note that everything I’ve said above regarding (non-Static) local variables also applies to ByVal parameters (including Me), but not ByRef parameters or fields (except those implicitly declared by the compiler for lambda closures, Async, and Iterator methods) maybe even ReadOnly ones, because their values can change on other threads or otherwise between when the variable is tested and when it is read. Alternately, some of these cases may be allowed for expressiveness but at the expensive of additional runtime checks at read-time. Please discuss.

The rest we can work out on GitHub during the specification phase where we talk about situations like aliasing, and the implications of Is checks (e.g. if TypeOf a Is {Bird Or Null} and TypeOf b Is {Fish Or Null} and a Is b does that mean that a Is Null and b Is Null? “Something, something, information”. And we can talk about explicit ByRef and Out arguments (Spoiler: I’m adding explicit ByRef and Out arguments). Oh, not to mention the debugging experience (read: limitations).

Finally, for a discussion of ad-hoc “anonymous” union types (including exhaustiveness checking) as well as intersection types and what it would mean if ModVB supported them explicitly in declarations (not just as a consequence of flow control), look forward to the Section Summary #14, “New Kinds of Types” hopefully some time this month, health allowing.

1.2        Conditional and Null-Coalescing (If) Operators

1.2.1        Target-Typing

Both the If(expression, trueValue, falseValue) and If(expression, valueIfNull) operators will be target-typed in ModVB. This means when used in a context that dictates a specific result type, the result operands will themselves be target-typed or converted to that overall result type rather than attempting to find a common type between them. This is an essential change to support intuitive behavior around the 5 typeless/target-typed expressions already in vanilla VB (AddressOf, Nothing, lambda expressions, array literals, and interpolated strings), as well as any added in ModVB (hint). For example,

' error BC36911: Cannot infer a common type.
Dim action As Action =
      If(condition, AddressOf Console.Beep, AddressOf Console.WriteLine)
'     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In vanilla VB this reports and error, requiring you to explicitly cast one or both operands to Action. In ModVB, each operand is necessarily target-typed to the target-type of the If operator (Action) and no error is reported.

1.2.2        Improved Dominant Type

Additionally, as discussed above, the If(condition, trueValue, falseValue) operator will be enhanced to infer the most derived common base class or interface when not target-typed and trueValue and falseValue don’t have a common type. This change should apply to any VB type inference which uses the “Dominant Type” algorithm (conditional expressions, array literals, type argument inference, and lambda functions).

1.2.3        Can Be an L-Value (Assignable)?

Lastly, it is worth discussing whether to change these operators to be L-values (assignable) when their result expressions are L-values, specifically to match the intuitive understanding of these operators as short-hand for the If statement

Let variableIfTrue As Object, variableIfFalse As Object

' Works.
If condition Then
    dictionary.TryGetValue("someKey", Out variableIfTrue)
Else
    dictionary.TryGetValue("someKey", Out variableIfFalse)
End If

' Analogous code fails silently in vanilla VB.
' Will either report an error in ModVB or work correctly.
dictionary.TryGetValue("someKey", Out If(condition,
                                         variableIfTrue, 
                                         variableIfFalse))

Honestly, now that I’ve written it out, it feels silly to ask, the right choice seems obvious, but it could be a little unexpected to folks coming from vanilla VB so, let’s discuss it.

1.3        Array Literals

Like the If operators, array literals (when not target-typed) will also be able to infer the most derived common base type or interface with one difference: they will only infer a simple type, no supplemental Ands, Ors, or Nots. It’s a verifiability thing

Let animals = {New Bat, New Bird, New HoneyBee}

You might be thinking the element type of the array is {Animal And ICanFly} but that type only exists to the compiler; the CLR does not have a notion of an intersection or union type so while your code may have only filled it with Animal instances that are also ICanFly instances, any other code, perhaps not written by you, will only see an array of Animal and may fill it with Lion, Tiger, or Bear instances. Yes, the compiler could be made to do some kind of escape analysis to determine that if the reference to the array never escaped the containing method (including by a Static local, lambda capturing, or Async or Iterator hoisting) it could safely retain the more complex type but 1) that’s a very different and more expensive feature, 2) it would restrict the use of the array so much that it wouldn’t be useful, I think. Much work, not much value. Happy to be convinced!

You’ll see similar restrictions elsewhere for basically the same reason.

1.4        Type Argument Inference

Type argument inference will likely have the same or greater restrictions as array literals with the same reasoning. Given the following

Module Cache(Of T)
    Public Shared Value As T
End Module

Function M(Of T)(a As T, b As T) As T
    Return Cache(Of T).Value        
End Function

Cache(Of Animal).Value = New Shark

Let result = M(New Bat, New HoneyBee)

It’s safe at the call-site of M to infer T as Animal but not that T is also ICanFly.

1.5        Multi-Line Function Lambdas

Multi-line Function lambdas (including Iterator lambdas), which also use the dominant type algorithm (when not target-typed), should also benefit from inferring the most derived common base class or interface when there is otherwise not a common type between Return or Yield expressions

' Dominant type only matters in a multi-line function lambda with multiple
' 'Return' or 'Yield' statements.
Let getFlyingAnimal =
      Function(mustMakeHoney As Boolean)
          If mustMakeHoney Then
              Return New Bee
          Else
              Return New Bird
          End If
      End Function

In the above, the type of the lambda expression is

<Function(Boolean) As {Animal, ICanFly, {Bee Or Bird}}>

Note that this inference does not have limitations on And or Or types as they are safe in this context. Furthermore, through VB’s normal conversion rules it should be possible to convert this lambda (or the variable getFlyingAnimal) to a nominal delegate type which returns Animal such as Func(Of Boolean, Animal), or returns ICanFly such as Func(Of Boolean, ICanFly).

1.6        Bug Fixes & Other Minutiae

  • This code will no longer error and will correctly infer the return type of Create
Let customer = Customer.Create()
  • Static local variables will correctly infer their type from initializers
Static cache = New Dictionary(Of Integer, Integer)

1.7        Wrap up

Yikes! That was a lot!

As you can see, ModVB will level-up VB’s already powerful type inference capabilities to make the language even more expressive with fewer explicit types, fewer temporary variable declarations, and fewer explicit casts required, all without sacrificing performance.

Thanks for reading! Please share and discuss everywhere!

Overview ↑ | Streamlining and Boilerplate Reduction

5 thoughts on “Type-Inference Enhancements

    • Roslyn code or other code? I worry the Roslyn type hierarchy has biased me. It’s still valid, of course, just would like more broad use cases.

      Like

      • Both Roslyn and other applications. How would below be simplified and easier to understand. This is just one example where the name/value formattedNumber is used multiple placed and in some cases formattedNumber could be used long after it is defined and possibly returned.

        Protected Overrides Function GetFormattedValue(value As Object, rowIndex As Integer, ByRef cellStyle As DataGridViewCellStyle, valueTypeConverter As TypeConverter, formattedValueTypeConverter As TypeConverter, context As DataGridViewDataErrorContexts) As Object ‘ By default, the base implementation converts the Decimal 1234.5 into the string “1234.5” Dim formattedValue As Object = MyBase.GetFormattedValue(value, rowIndex, cellStyle, valueTypeConverter, formattedValueTypeConverter, context) Dim formattedNumber As String = TryCast(formattedValue, String) If Not String.IsNullOrEmpty(formattedNumber) AndAlso value IsNot Nothing Then Dim unformattedDecimal As Decimal = Convert.ToDecimal(value) Dim formattedDecimal As Decimal = Convert.ToDecimal(formattedNumber) If unformattedDecimal = formattedDecimal Then ‘ The base implementation of GetFormattedValue (which triggers the CellFormatting event) did nothing else than ‘ the typical 1234.5 to “1234.5” conversion. But depending on the values of ThousandsSeparator and DecimalPlaces, ‘ this may not be the actual string displayed. The real formatted value may be “1,234.500” Return formattedDecimal.ToString($”{If(_thousandsSeparator, “N”, “F”)}{_decimalPlaces}”) End If End If Return formattedValue End Function

        Like

  1. I like this language feature. It will fix a lot of BC42016 warnings in my VB projects.

    By the way, I use smart casts frequently in Kotlin projects. Because it can save the time of thinking the new variable name of the converted local variables.

    Like

    • Yes, one user observed that relying solely on pattern matching for this scenario meant people using a lot of throw away 1-letter variable names like `s` and `t` so I like that this doesn’t require names too.

      Like

Leave a comment