Turning on Option Strict - Pitfalls? - vb.net

I'm turning On Option Strict on all project in my newly inherited VB.NET application. I'm mostly adding alot of CStr, CBool, CType statements to get rid of all the compile errors.
Dim x As String = someObject
dim val As SomeEnumType = 1
becomes
Dim x As String = CStr(someObject) ' Not .ToString() because someObject could be Nothing
Dim val As SomeEnumType = CType(1, SomeEnumType)
etc.
I'm doing everything pretty much by hand one error at a time and have a test application to test the Nothing, ... bordercases.
But is it possible I'm missing something that is going to generate exceptions at runtime?
And what kind of code is being generated due to Option Strict? Is it just some conversions that are going to be added or does OptionStrict do other things aswell?

Option Strict On does not generate any extra code, it merely tells the compiler to generate errors when your vb.net statements are relying on implicit type conversions. Like assigning an object to a string. What you've written in your snippet is exactly what the compiler does with Option Strict Off so no extra code is generated by your type conversion operators.
But of course, there's always a non-zero chance that you use the wrong conversion and break the existing code. You'll have to do what's always required when you make changes to code, you'll have to re-test it.

Related

Strict Properties Without Option Strict On

When Option Strict is turned Off, which is the default for my application, VB will automatically convert the value when property is String. It appears to call the toString() of the incoming value's Object. I believe casting will work for any property that can be safely casted (eg integer to double). I want the properties to behave in a strict manner, in that the Type passed MUST match the type on the declared property.
Is there a way to make Objects Properties Strict at the Object level without having to resort to the Option Strict On configuration on?
For example:
Option Strict Off
Class TestObj
Private _foo As String
Public Property Foo as String
Get
Return _foo
End Get
Set(v As String)
console.write(String.format("v = <{0}> {1}", v.GetType().FullName, v))
_foo = v
End Set
End Property
End Class
Dim o as new TestObj()
o.Foo = "some_str"
o.Foo = 1234
o.Foo = DateTime.now()
In the above case, all values are converted to Strings at arrival to the setter:
v = <System.String> some_str
v = <System.String> 1234
v = <System.String> 7/7/2022 8:22:49 PM
I am aware of DynamicObject and ability to tightly control setters like Python's __setattr__ but this breaks autocomplete functionality in editors as it would preclude defining properties on the class to get the functionality.
I strongly agree with the commenters that Option Explicit On and Option Strict On are simply a must - as the default settings of Visual Studio for new VB projects as well as for each project within the solution(s) you took over.
I my opinion one has to invest first some effort into clean-up work when taking over legacy solutions including
Add an ".editorconfig" if there isn't any
Convert the projects to SDK projects where possible
Go through the code and
format the code to your liking using the current naming convention where possible
correct/complement/remove/translate-to-English the documentation comments
add some //TODO: - comments where refactoring is needed wherever you notice a code repetition, repeated property access, huge methods that needs to be split into smaller parts, if-not-condition-then-else that needs to be reverted etc. but don't code it yet - these refactorings could introduce some bugs, we need to finish the next step first.
put every top-level type in its own file (except overloaded generic types of the same name) and make sure the type name matches the file name
Go through the unit tests and fix them, remove unnecessary ones and complement the missing ones.
Change all projects to Option Explicit On, Option Strict On and Option Compare Binary (use explicit StringComparer.OrdinalIgnoreCase / StringComparer.InvariantCultureIgnoreCase where needed). (Option Infer On or Off, what you prefer). If you hate to have 1000s of errors, add an Option Strict Off on top of each VB file which you later removed again file by file, otherwise do a bulk mutation.
Fix all compile errors (per file or bulk mutation)
Run unit tests, fix code of failing tests (repeat this after each of the following steps)
Update the .NET framework version if possible (e.g. to current or .NET 4.8)
Update the NuGet packages
Do all the previously tagged refactorings
Enable the analyzer warnings
Fix all the warnings and infos or suppress them, individually or in the project file by adding the according entry to the <NoWarn> element.
Start implementing the changes why you originally cloned the repository...
Of course that comes with a price but not doing it also has its price - I'm certain you are going to find dozens of little mistakes in the original code during the process of going Strict. And the time one spends formatting the code files is not just wasted time neither as in the process one also learns to understand the code...

How serious is BC42020 in an upgraded VB .Net project?

Consider the following line of code which used to compile without warnings.
Public SetUpDone = False
After upgrading a project to Visual Studio 2017 from Visual Studio 2005 over a hundred of these BC42020 warnings exist. The MSDN description of the warning simply states that the variable defaults to type object. I don't have a good idea of the seriousness of this type of warning. The debugger indicates that the code executes as I expect. Is it merely a performance type of issue?
Secondly, I thought that Visual Basic supported some form of Type Inference so I'm not clear about why it wouldn't be able to deduce that the type should be Bool.
Another example is the following where the function returns a String
Dim dayTxt = " " & GetTextFromIni("General", "Var50")
I would have thought that type inference would work here and deduce that dayTxt is a String.
This:
Public SetUpDone = False
Is equivalent to this:
Public SetUpDone As Object = False
As suggested, type inference is only for local variables, not fields. With Option Infer On, this inside a method:
Dim SetUpDone = False
would indeed be equivalent to this:
Dim SetUpDone As Boolean = False
There are a couple of issues with the code as you have it. Firstly, it means that every use of that False value requires unboxing which makes your code slower. That's the case for any value types, i.e. structures. Value types are normally stored on the stack but, when boxed, are stored on the heap.
Secondly, it means that any member access will require late binding. That's not an issue for Boolean values because they have no members of interest anyway but if it was, say, a DateTime then the IDE would never provide Intellisense for that type because al it would see would be type Object and the existence of the specified member would have to be confirmed at run time, making the code less efficient again.
Thirdly, it means that the compiler can never confirm that you're passing the correct type as a method argument. For instance, if you have a method with a Boolean parameter, the compiler won't know that you're passing a Boolean if you pass that field because it's type Object. That also means that if you pass some other Object variable that doesn't contain a Boolean, the compiler can't warn you.
As suggested, you should turn Option Strict On in the project properties. That will flag every instance of you're not specifying the appropriate type for something. Fixing those errors will, at the very least, make your code a bit more efficient. It may even draw your attention to situations where exceptions will or could be thrown at run time. Having Option Strict On enforces strict typing so it makes you think more about the types you're using. Even if you're conscientious about that with Option Strict Off, you can still make mistakes that Option Strict On will prevent.

Implicit Interface casts of Nullables

With VB's Option Strict On, why does a Nullable(Of T) not require an explicit cast to an interface of T when it does require one to T?
I.e.
Dim x As Integer? = 5
Dim y As Integer
Dim z As IComparable
y = x ' Fails to compile with error
' "Option Strict On disallows implicit conversions from 'Integer?' to 'Integer'."
z = x ' Succeeds
EDIT: As (sort of) shown by #SSS, part of the answer is that Nullable values are, well, nullable, and can be Nothing, which is fine for a reference like an interface. So this conversion will always succeed, unlike the conversion to T case (which fails when the Nullable has no value), and so it can be seen as an implicit conversion.
My question now becomes "how?". How is the conversion from a Nullable(Of T) (which has no interfaces of its own) to an interface of T theoretically negotiated?
I know the implementation is box Nullable<T>, which effectively strips the Nullable wrapper, but I'm confirming the concept here...
(So I'll review the documentation and see if they explain this.)
I don't see the problem?
y = x
can fail because x could hold a value of Nothing, but y is not allowed to hold a value of Nothing. The IComparable interface allows Integers to be compared to Nothing however, so that assignment is fine.
Notice that if you swap it round:
x = y
then this succeeds because every value of y can be assigned to x.
You can confirm that Integers can be compared to Nothing as follows:
MsgBox(5.CompareTo(Nothing))
From what I can tell in vb.net, the statement interfaceVariable = nullableVariable is essentially equivalent to interfaceVariable = if(nullableVariable.HasValue, CType(nullableVariable.Value, interfaceType), Nothing). The C# compiler seems to handle things the same way: interfaceVariable = nullableVariable; becomes interfaceVariable = nullableVariable.HasValue ? (interfaceType)nullableVariable.Value : null;.
If the type of nullableValue.Value implements the interface, then nullableVariable.Value will either perform return a value-type result or throw an exception. Since there exists a guaranteed boxing conversion from the return value to the interface, the cast will be legal. The only way the above code could fail would be if the nullable variable gets written between the calls to HasValue and Value, such HasValue sees the variable as non-null, but Value sees it as null and throws an exception. I believe that writing interfaceVariable = nullableVariable just tests nullity once, so that an exception could not occur; instead, an indeterminate value would get boxed.
Without actually reading documentation yet, I'm going to attempt an answer:
Firstly, the higher-level answer is that casting a Nullable to an interface is "safe" and will not throw, so it is logically a Widening operator and should not need to be explicit (compared to casting to T, when .HasValue is False it throws, so it should not be implicit with Option Strict On).
However, technically the "how" is a bit obscure: Even though some of the behaviour of Nullable is encoded in the metadata available via reflection, much of its "magic" is hidden in:
the runtime behaviour of box on a Nullable (and thus the compiler knows when to leave "lifting" to that), and
the other points made by Eric Lippert in his answer for C# and their equivalent in VB.NET.
It looks like S. Somasegar's blog post announcing changes to Nullable support in a late beta release for VS2k5 is also relevant here.

How do I use Thread.VolatileWrite with reference types with Option Strict On?

Wrapping the argument in CObj or DirectCast shuts the compiler up, but the value is still not written.
Option Strict On
Imports System.Threading
Module Module1
Dim str As String
Sub Main()
Thread.VolatileWrite(str, "HELLO") ' Compiler error.
Thread.VolatileWrite(CObj(str), "HELLO") ' Fails silently.
Thread.VolatileWrite(DirectCast(str), "HELLO") ' Fails silently.
Console.WriteLine(str)
End Sub
End Module
There is no overload of Thread.VolatileWrite which takes a String argument. The only reference type supported is Object.
Because VolatileWrite is updating the variable str and Option Strict is On the compiler complains because in theory VolatileWrite could attempt to write a value to that variable which is not of type String (the compiler only sees that it might write any Object). In fact, as the VolatileWrite method also only takes a String you could write code which would attempt to do this. It would fail for reasons beyond the scope of this question.
When you wrap the expression in a COjb/CType/DirectCast expression (really anything with parenthesis) then the variable is no longer considered a variable but a value - it's treated the same way as if you'd just type a string literal there. Since values don't have storage locations the ByRefness of VolatileWrite is ignored which means it no longer writes which means it can no longer write a bad value which means the compiler doesn't need to warn anymore.
To get the behavior you want with a string type variable use the System.Threading.Thread.MemoryBarrier method before your writes and after your reads. See this thread for additional information: How do I specify the equivalent of volatile in VB.net?

Is it good practice to define "what my variable will be"?

So I have this:
Dim aBoolean As Boolean = True
Will it make any difference to just do this?
Dim aBoolean = True
In other languages, I believe it was a good practice to also define the type of variable it would be, for performance or something. I am not entirely sure with VB.NET.
Thanks.
It depends. Explicitly defining the variable can improve readability, but I don't think it's always necessary. (To be clear, it has nothing to do with the actual functionality of your code).
In this specific example, you follow the declaration with a Boolean assignment of True, so it's already crystal clear that aBoolean is actually a Boolean when it is declared. The As Boolean syntax is not as necessary in this scenario.
Other cases may not be so clear. If the declaration was followed by the result of a function call, for example, it might be more clear to explicitly declare that the variable is a Boolean. e.g.
Dim aBoolean As Boolean = TestValidityOfObject(o)
As long as you have Option Infer turned on, it won't make a bit of difference. The second line is just a syntactic abbreviation for the first. At that point, it's up to your style preference as to which you should use.
Before type inference, there were performance issues when not declaring the type, but that's no longer an issue; due to type inference the variable will be of type Boolean whether you declare it or not.
Declaring the type can help the compiler catch errors sooner, and will often give you better Intellisense.
You're using what's called "type inference". This is where the compiler figures out at compile time what the type on the right side of the assignment is and uses that as the type of the variable.
This is, in general, a safe and convenient feature. However, there are a couple of things to keep in mind:
You must have Option Infer on; otherwise, the compiler doesn't do type inference and, depending on your setting for Option Strict, instead either gives you a compile time error (Option Strict On) or types your variable as Object and uses late binding everywhere. This is Pure Evil. (Option Strict Off)
In your particular case, there's no way for the compiler to mess up. HOWEVER, it's possible to use type inference in such a way as to change the semantics of your code:
For instance...
Dim myClass as MyBaseClass = New SubClass()
This is perfectly legal; we're typing the variable as a base class and assigning a value to it that represents an instance of a subclass. Nothing special. However, if we switch to type inference by just removing the type declaration...
Dim myClass = New SubClass()
Type inference will now see myClass as a SubClass instead of MyBaseClass. This might seem obvious, but the point is that you should be aware of what it's doing.
For more information and long-winded discussion about using type inference, see this question. While that question is targeted at C#, the only real difference is the first item that I listed above. Everything else is conceptually the same.