Type Conversions
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-19. A conversion changes how a value
is represented, so that it can be stored in a variable or passed to a parameter of another type. Visual Basic has more conversion
syntax than most languages, and each form has different rules for what it accepts, what it rounds, which culture it reads and which exception
it throws: the sixteen conversion functions (CInt and its relatives), the operators CType, DirectCast and TryCast, the
type test TypeOf ... Is, the Convert class, Parse and TryParse, and conversion operators that a type defines
for itself. This page puts them side by side. The rule that decides how many of them are allowed implicitly is
Option Strict, described on Option Statements; the types themselves on
Data Types and Literals.
The page covers the following, in this order: widening and narrowing with the complete matrix produced by the compiler; the conversion
functions; the cast operators; text and culture; user-defined conversions; variance; enumerations and Boolean; a decision
flowchart; the exceptions; and the differences from C#. Operators and their result types are on
Operators and Expressions and string formatting on
Strings and Text.
Every example is a complete program that was built with Option Strict On and warnings as errors on the .NET 10 SDK and run; the
output is the real one. The programs are small by design, and use the shelf catalogue’s types (CatalogItem, Book, Magazine,
Isbn, LoanState). Examples that are meant not to compile are labelled and show the compiler’s messages.
Widening and narrowing
A conversion is widening when every value of the source type can be represented in the target type’s range, and narrowing when
some cannot. Learn’s definition adds that a widening conversion can still lose precision (Long to Double keeps the magnitude but not all the digits), so
widening is a statement about range and not about exactness. A conversion is implicit when the compiler applies it without being asked, and
explicit when the source code asks for it with a function or an operator. The rules:
-
Widening conversions are always allowed implicitly, and never throw.
-
Narrowing conversions are allowed implicitly only with
Option Strict Off, where a failure appears at run time. WithOption Strict Onevery narrowing conversion needs an explicit conversion (BC30512 otherwise). -
A constant that fits the target type converts implicitly in either direction (
Dim b As Byte = 200). -
The
For Eachloop variable is the documented exception: its narrowing conversion from the element type is not checked at compile time.
The compiler can tell which pair is which, so the matrix below was generated: a program of 240 one-line assignments, Dim t As Target = v
for every ordered pair of the sixteen elementary types with v of the source type, was built with Option Strict On, and the pairs that produced no error are widening. It was
built only; the 179 diagnostics are the pairs that are not.
| From | Implicit widening conversions that Option Strict On accepts |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Read the table for four things:
-
The integer chains:
Bytewidens to every other numeric type exceptSByte;Shortdoes not widen toUShort;Integerdoes not widen toUInteger; a signed type never widens to an unsigned one and the reverse needs a larger signed type (UIntegertoLong). -
LongandULongwiden toSingleandDouble, andDecimalwidens to both. The value stays in range; the low digits may not survive. -
Charwidens toString, and aStringdoes not narrow toCharimplicitly.Datedoes not widen to anything butObject. -
Everything widens to
Object(the last column of each row), andObjectnarrows to everything.
The pairs that are missing are of three kinds, with different messages. The compiler’s wording is part of the diagnosis:
| Code | Message | Meaning |
|---|---|---|
BC30512 |
Option Strict On disallows implicit conversions from 'Double' to 'Integer'. |
A narrowing conversion exists. Write the conversion. |
BC30311 |
Value of type 'Byte' cannot be converted to 'Date'. |
No conversion exists at all between the types, with any syntax. |
BC32006 and BC32007 |
'Char' values cannot be converted to 'Byte'. Use 'Microsoft.VisualBasic.AscW' to interpret a character as a Unicode value or 'Microsoft.VisualBasic.Val' to interpret it as a digit. And the reverse: 'Byte' values cannot be converted to 'Char'. Use 'Microsoft.VisualBasic.ChrW'. |
|
BC30532 and BC30533 |
Conversion from 'Date' to 'Double' requires calling the 'Date.ToOADate' method. And: Conversion from 'Double' to 'Date' requires calling the 'Date.FromOADate' method. |
The Visual Basic 6 habit of treating a date as a number of days is not available implicitly. |
Everything else the compiler reports is BC30512 (129 pairs) or BC30311 (32 pairs); the remaining pairs split between BC32006 or BC32007 (16 pairs) and BC30532 or BC30533 (2 pairs).
Plus is not concatenation
Visual Basic converts the operands of + toward numbers when one of them is a string, which is the origin of the best-known conversion bug of the language.
With Option Strict On the compiler stops it, and the messages show the two conversions that it refused:
Option Strict On
Module Program
Sub Main()
Dim copies As Integer = 3
Dim label As String = "Copies: " + copies
Dim total As Integer = "5" + 1
Dim joined As String = "Copies: " & copies
Dim sum As String = "5" & 1 & 2
Dim d As Date = #10/19/2026# + 1
End Sub
End Module
Program.vb(10,25): error BC30452: Operator '+' is not defined for types 'Date' and 'Integer'.
Program.vb(6,31): error BC30512: Option Strict On disallows implicit conversions from 'Double' to 'String'.
Program.vb(6,31): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Double'.
Program.vb(7,32): error BC30512: Option Strict On disallows implicit conversions from 'Double' to 'Integer'.
Program.vb(7,32): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Double'.
Source: Concatenation Operators
Each of lines 6 and 7 reports two BC30512 errors: the String operand would be converted to Double and the Double result converted back. The two
& lines compile, because every conversion to a string by the concatenation operator is considered widening. The last line shows that Date + Integer has
no meaning at all (BC30452, "Operator '+' is not defined"); Date arithmetic uses AddDays and its relatives. Quickstart Guide pp. 18 and 134 concatenate
with +; the correct operator is &, or an interpolated string.
The conversion functions
The sixteen conversion functions are keywords of the language, one per elementary type. Each converts its argument to the target type, and the compiler inlines the common cases. They are the usual way to write an explicit conversion between built-in types.
| Function | Returns | Notes |
|---|---|---|
|
|
Zero is |
|
|
Rounds, then range-checks 0 to 255. |
|
|
The first character of a string. An empty string gives |
|
|
Parses the string with the current culture; |
|
|
Accepts exponents ( |
|
|
From |
|
|
Rounds half to even. Strings may be decimal or carry a |
|
|
As |
|
|
Boxes a value type; never fails. |
|
|
Range-checks -128 to 127. |
|
|
As |
|
|
Rounds to the nearest |
|
|
|
|
|
Negative values throw |
|
|
As |
|
|
As |
Source: Type Conversion Functions
The program below applies the functions to values from the shelf scenario (a quantity typed as text, a price, a due date) and prints
the result or the exception. Every Attempt call runs the conversion on a variable, not on a constant, because the compiler folds constant
conversions and reports their errors at build time:
Option Strict On
Imports System.Globalization
Module Program
Private Sub Attempt(label As String, action As Func(Of Object))
Dim shown As String
Try
Dim result As Object = action()
shown = TypeName(result) & " " & Convert.ToString(result, CultureInfo.InvariantCulture)
Catch ex As Exception
shown = ex.GetType().Name & ": " & ex.Message
End Try
Console.WriteLine(label.PadRight(26) & shown)
End Sub
Sub Main()
Dim text As String = "12"
Dim blank As String = ""
Dim maybe As Object = Nothing
Dim threeHundred As Integer = 300
Dim minusOne As Integer = -1
Dim big As Long = 3000000000L
Dim fraction As Double = 3.99
Dim notANumber As Double = Double.NaN
Dim twoPointFive As Double = 2.5
' To Integer, from strings, Nothing, Boolean and floating point.
Attempt("CInt(""12"")", Function() CInt(text))
Attempt("CInt(""&H1F"")", Function() CInt("&H1F"))
Attempt("CInt("" 12 "")", Function() CInt(" 12 "))
Attempt("CInt("""")", Function() CInt(blank))
Attempt("CInt(""abc"")", Function() CInt("abc"))
Attempt("CInt(Nothing)", Function() CInt(maybe))
Attempt("CInt(3.99)", Function() CInt(fraction))
Attempt("CInt(True)", Function() CInt(True))
Attempt("CInt(3000000000L)", Function() CInt(big))
Attempt("CInt(NaN variable)", Function() CInt(notANumber))
Attempt("CByte(300)", Function() CByte(threeHundred))
Attempt("CByte(-1)", Function() CByte(minusOne))
Attempt("CUInt(-1)", Function() CUInt(minusOne))
Attempt("CSByte(300)", Function() CSByte(threeHundred))
' Boolean, Char and String.
Attempt("CBool(0) CBool(5)", Function() CBool(0).ToString() & " " & CBool(threeHundred).ToString())
Attempt("CBool(""true"")", Function() CBool("true"))
Attempt("CBool(""1"")", Function() CBool("1"))
Attempt("CBool(""yes"")", Function() CBool("yes"))
Attempt("CChar(""hello"")", Function() CChar("hello"))
Attempt("AscW(CChar(""""))", Function() AscW(CChar(blank)))
Attempt("CStr(True)", Function() CStr(True))
Attempt("CStr(1.5)", Function() CStr(1.5))
Attempt("CStr(Nothing)", Function() CStr(maybe) Is Nothing)
' Floating point, Decimal and Date.
Attempt("CDbl(""1.5e2"")", Function() CDbl("1.5e2"))
Attempt("CDec(""19.99"")", Function() CDec("19.99"))
Attempt("CDec(0.1)", Function() CDec(0.1))
Attempt("CSng(1 / 3)", Function() CSng(1 / 3).ToString("R", CultureInfo.InvariantCulture))
Attempt("CDate(""2026-10-19"")", Function() CDate("2026-10-19"))
Attempt("CDate(""31/12/2026"")", Function() CDate("31/12/2026"))
Attempt("CObj(5)", Function() CObj(5).GetType().Name)
Attempt("CULng(2.5)", Function() CULng(twoPointFive))
End Sub
End Module
CInt("12") Integer 12
CInt("&H1F") Integer 31
CInt(" 12 ") Integer 12
CInt("") InvalidCastException: Conversion from string "" to type 'Integer' is not valid.
CInt("abc") InvalidCastException: Conversion from string "abc" to type 'Integer' is not valid.
CInt(Nothing) Integer 0
CInt(3.99) Integer 4
CInt(True) Integer -1
CInt(3000000000L) OverflowException: Arithmetic operation resulted in an overflow.
CInt(NaN variable) OverflowException: Arithmetic operation resulted in an overflow.
CByte(300) OverflowException: Arithmetic operation resulted in an overflow.
CByte(-1) OverflowException: Arithmetic operation resulted in an overflow.
CUInt(-1) OverflowException: Arithmetic operation resulted in an overflow.
CSByte(300) OverflowException: Arithmetic operation resulted in an overflow.
CBool(0) CBool(5) String False True
CBool("true") Boolean True
CBool("1") Boolean True
CBool("yes") InvalidCastException: Conversion from string "yes" to type 'Boolean' is not valid.
CChar("hello") Char h
AscW(CChar("")) Integer 0
CStr(True) String True
CStr(1.5) String 1.5
CStr(Nothing) Boolean True
CDbl("1.5e2") Double 150
CDec("19.99") Decimal 19.99
CDec(0.1) Decimal 0.1
CSng(1 / 3) String 0.33333334
CDate("2026-10-19") Date 10/19/2026 00:00:00
CDate("31/12/2026") InvalidCastException: Conversion from string "31/12/2026" to type 'Date' is not valid.
CObj(5) String Int32
CULng(2.5) ULong 2
What the output teaches:
-
CInt("&H1F")is 31: a string with a radix prefix is parsed as hexadecimal, which neitherInteger.Parsenor C# does. -
CInt(Nothing)is 0, butCInt("")isInvalidCastException. A nullStringorObjectreads as zero; an empty string is not a number. -
CInt(3.99)is 4: the conversion functions round to the nearest, they do not truncate (next section). -
CInt(True)is -1 (see the section onBoolean). -
CInt(3000000000L),CByte(300),CByte(-1),CUInt(-1)andCSByte(300)throwOverflowException, "Arithmetic operation resulted in an overflow.", and so does a floating-pointNaNstored in a variable. The compiler folds conversions of constants:CInt(Double.NaN)written with the constant compiled and was folded to 0 by the compiler with no diagnostic in an earlier build of this program, whereas the same conversion of a variable throws. Do not use a conversion of a constant to test the run-time behaviour. -
CBool("yes")throwsInvalidCastException, "Conversion from string "yes" to type 'Boolean' is not valid." -
CChar("")is the NUL character (code 0), andCChar("hello")ish. -
CDbl("1.5e2")is 150 andCDec(0.1)is exactly0.1. -
CDate("31/12/2026")fails here because the process runs with the invariant culture, whose short date is month first.
Banker’s rounding
When the argument is not integral, the integer conversion functions round to the nearest integer, and a value exactly halfway rounds to the
even neighbour (banker’s rounding, IEEE 754 round half to even): 0.5 becomes 0, 1.5 becomes 2, 2.5 becomes 2 and 3.5 becomes 4. This is a
difference from C#, whose cast (int)x truncates toward zero. The next program prints the rounding family together, so you can pick the one you mean:
Option Strict On
Imports System.Globalization
Module Program
Sub Main()
Dim inv As CultureInfo = CultureInfo.InvariantCulture
Dim values As Double() = {-2.5, -1.5, -0.7, -0.5, 0.5, 1.5, 2.5, 2.7, 3.5}
Console.WriteLine("value CInt Math.Round AwayFromZero Fix Int Floor Ceiling Truncate")
For Each v In values
Console.WriteLine(v.ToString("0.0", inv).PadLeft(5) & " " &
CInt(v).ToString().PadLeft(4) & " " &
Math.Round(v).ToString("0", inv).PadLeft(10) & " " &
Math.Round(v, MidpointRounding.AwayFromZero).ToString("0", inv).PadLeft(12) & " " &
Fix(v).ToString("0", inv).PadLeft(3) & " " &
Int(v).ToString("0", inv).PadLeft(3) & " " &
Math.Floor(v).ToString("0", inv).PadLeft(5) & " " &
Math.Ceiling(v).ToString("0", inv).PadLeft(7) & " " &
Math.Truncate(v).ToString("0", inv).PadLeft(8))
Next
' Integer division truncates toward zero and works on integers; / returns a Double first.
Dim total As Integer = 7
Dim count As Integer = 2
Console.WriteLine("7 \ 2 = " & (total \ count).ToString() & ", -7 \ 2 = " & (-total \ count).ToString() &
", CInt(7 / 2) = " & CInt(total / count).ToString() & ", CInt(-7 / 2) = " & CInt(-total / count).ToString())
' Decimal rounds the same way: banker's rounding to even.
Console.WriteLine("CInt(2.5D) = " & CInt(2.5D).ToString() & ", CInt(3.5D) = " & CInt(3.5D).ToString() & ", Math.Round(2.675D, 2) = " & Math.Round(2.675D, 2).ToString(inv))
' The optimized pattern of Visual Basic 16: the argument is already integral.
Dim s As Single = 173.7619F
Dim d As Double = s
Console.WriteLine(CInt(Fix(s)).ToString() & " " & CByte(Int(d)).ToString() & " " & CShort(Math.Truncate(s)).ToString() & " " &
CInt(Math.Ceiling(d)).ToString() & " " & CInt(Math.Round(s)).ToString())
End Sub
End Module
value CInt Math.Round AwayFromZero Fix Int Floor Ceiling Truncate
-2.5 -2 -2 -3 -2 -3 -3 -2 -2
-1.5 -2 -2 -2 -1 -2 -2 -1 -1
-0.7 -1 -1 -1 -0 -1 -1 -0 -0
-0.5 0 -0 -1 -0 -1 -1 -0 -0
0.5 0 0 1 0 0 0 1 0
1.5 2 2 2 1 1 1 2 1
2.5 2 2 3 2 2 2 3 2
2.7 3 3 3 2 2 2 3 2
3.5 4 4 4 3 3 3 4 3
7 \ 2 = 3, -7 \ 2 = -3, CInt(7 / 2) = 4, CInt(-7 / 2) = -4
CInt(2.5D) = 2, CInt(3.5D) = 4, Math.Round(2.675D, 2) = 2.68
173 173 173 174 174
Source: Math.Round
Compare the columns row by row. CInt and Math.Round(v) agree: both round half to even, -2.5 to -2 and 2.5 to 2. MidpointRounding.AwayFromZero is the school rule (2.5 to 3, -2.5 to -3)
and the one to use for money when the business says "round half up". Fix and Math.Truncate drop the fraction toward zero; Int and Math.Floor go toward negative
infinity (-2.5 gives -3); Math.Ceiling toward positive infinity. Several cells print -0: Fix, Math.Ceiling, Math.Truncate and Math.Round of values between -1 and 0 return the Double negative zero, and the formatting of
.NET Core 3.0 and later shows its sign. The last lines show that the integer division \ truncates toward zero (-7 \ 2 is -3) and that CInt(7 / 2) rounds the
Double 3.5 to 4: the same two numbers, two different answers. Decimal rounds the same way as Double: CInt(2.5D) is 2 and CInt(3.5D) is 4.
Fix, Int and the Visual Basic 16 optimization
Fix and Int are runtime-library functions that return the integral part of a number: Fix drops the fraction (toward zero), Int rounds toward negative infinity. Both
return the same type as the argument, so Fix(173.7619) is the Double 173 and a conversion is needed to store it in an Integer. Visual Basic 16.0 (2019) made the
combination fast: when the argument of CByte, CShort, CInt, CLng, CSByte, CUShort, CUInt or CULng is the result of Fix, Int, Math.Ceiling, Math.Floor, Math.Round or
Math.Truncate on a Double or Single, the conversion is much faster; Learn says up to twice as fast for code with many
conversions, and that the optimization truncates rather than rounds. The argument is already a whole number in these cases, so the value is the same either way. The last output line of the program above applies the five pairs of the
documentation example to 173.7619 and prints 173 173 173 174 174.
Source: What’s new for Visual Basic
CType, DirectCast and TryCast
The three operators convert between any types, not only built-in ones, and differ in what they are allowed to do:
| Operator | What it does | Fails with | Typical use |
|---|---|---|---|
|
Chooses the conversion at compile time from the static types: a built-in conversion, a user-defined one, a reference conversion or unboxing; with an |
|
The general explicit conversion, and the only operator that uses user-defined conversions. |
|
A reference cast or an unboxing. No helper is called and nothing is converted: the run-time type of the object must be |
|
Downcasts that you know are right, and unboxing. Learn notes that it has a small performance advantage over |
|
As |
Nothing; but |
A downcast that may legitimately be wrong, followed by |
|
|
Nothing; the left operand must have a reference type (BC30021 for an |
The guard before a |
Source: DirectCast Operator
The program (built in a project whose root namespace is Shelf, which is why the exception messages say Shelf.Book) uses the shelf’s CatalogItem, Book and Magazine. item is statically a CatalogItem that holds a Book, magazine holds a Magazine, anything is a boxed Integer and
shortValue a boxed Short. Each line shows what the expression gives or which exception it throws, with the message of the exception:
Option Strict On
Public MustInherit Class CatalogItem
Public ReadOnly Property Title As String
Protected Sub New(title As String)
Me.Title = title
End Sub
End Class
Public Class Book
Inherits CatalogItem
Implements IComparable
Public Sub New(title As String)
MyBase.New(title)
End Sub
Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
Return String.CompareOrdinal(Title, DirectCast(obj, Book).Title)
End Function
End Class
Public Class Magazine
Inherits CatalogItem
Public Sub New(title As String)
MyBase.New(title)
End Sub
End Class
Module Program
Private Sub Attempt(label As String, action As Func(Of Object))
Try
Dim result As Object = action()
Console.WriteLine(label.PadRight(40) & If(result Is Nothing, "Nothing", result.ToString()))
Catch ex As Exception
Console.WriteLine(label.PadRight(40) & ex.GetType().Name & ": " & ex.Message)
End Try
End Sub
Sub Main()
Dim dune As New Book("Dune")
Dim item As CatalogItem = dune
Dim anything As Object = 42
Dim shortValue As Object = CShort(42)
Dim magazine As CatalogItem = New Magazine("Wired")
Dim nothingItem As CatalogItem = Nothing
' Widening reference conversions are implicit; narrowing ones need a cast.
Attempt("DirectCast(item, Book).Title", Function() DirectCast(item, Book).Title)
Attempt("DirectCast(magazine, Book)", Function() DirectCast(magazine, Book))
Attempt("DirectCast(nothingItem, Book)", Function() DirectCast(nothingItem, Book))
Attempt("DirectCast(item, IComparable)", Function() DirectCast(item, IComparable) IsNot Nothing)
Attempt("TryCast(item, Book) Is Nothing", Function() TryCast(item, Book) Is Nothing)
Attempt("TryCast(magazine, Book) Is Nothing", Function() TryCast(magazine, Book) Is Nothing)
Attempt("CType(item, Book).Title", Function() CType(item, Book).Title)
' Unboxing needs the exact type with DirectCast; CType and CInt convert.
Attempt("DirectCast(anything, Integer)", Function() DirectCast(anything, Integer))
Attempt("DirectCast(shortValue, Integer)", Function() DirectCast(shortValue, Integer))
Attempt("CType(shortValue, Integer)", Function() CType(shortValue, Integer))
Attempt("CInt(shortValue)", Function() CInt(shortValue))
Attempt("DirectCast(anything, Long)", Function() DirectCast(anything, Long))
Attempt("CLng(anything)", Function() CLng(anything))
Attempt("CInt(CObj(item))", Function() CInt(CObj(item)))
' Testing the type first, then casting.
If TypeOf item Is Book Then
Console.WriteLine("TypeOf item Is Book -> " & DirectCast(item, Book).Title)
End If
If TypeOf magazine IsNot Book Then
Console.WriteLine("TypeOf magazine IsNot Book -> " & magazine.GetType().Name)
End If
Dim asBook As Book = TryCast(magazine, Book)
If asBook Is Nothing Then Console.WriteLine("TryCast gave Nothing, no exception")
End Sub
End Module
DirectCast(item, Book).Title Dune
DirectCast(magazine, Book) InvalidCastException: Unable to cast object of type 'Shelf.Magazine' to type 'Shelf.Book'.
DirectCast(nothingItem, Book) Nothing
DirectCast(item, IComparable) True
TryCast(item, Book) Is Nothing False
TryCast(magazine, Book) Is Nothing True
CType(item, Book).Title Dune
DirectCast(anything, Integer) 42
DirectCast(shortValue, Integer) InvalidCastException: Unable to cast object of type 'System.Int16' to type 'System.Int32'.
CType(shortValue, Integer) 42
CInt(shortValue) 42
DirectCast(anything, Long) InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Int64'.
CLng(anything) 42
CInt(CObj(item)) InvalidCastException: Conversion from type 'Book' to type 'Integer' is not valid.
TypeOf item Is Book -> Dune
TypeOf magazine IsNot Book -> Magazine
TryCast gave Nothing, no exception
Source: TryCast Operator
Notes on the output:
-
DirectCast(magazine, Book)throwsInvalidCastExceptionwith the CLR’s wording: "Unable to cast object of type 'Shelf.Magazine' to type 'Shelf.Book'." -
DirectCast(nothingItem, Book)isNothing: casting a null reference succeeds. -
DirectCast(anything, Integer)unboxes theIntegerthat is in the box, butDirectCast(shortValue, Integer)fails: "Unable to cast object of type 'System.Int16' to type 'System.Int32'." The box holds aShort, and an unbox has to name the exact type.CType(shortValue, Integer)andCInt(shortValue)succeed because they go through the conversion helpers, andCLng(anything)converts anIntegerbox toLongwhereDirectCast(anything, Long)fails. -
CInt(CObj(item))fails with "Conversion from type 'Book' to type 'Integer' is not valid.", a message from the Visual Basic helpers. The same call written withoutCObjis rejected at compile time (next listing). -
TypeOf item Is Book,TypeOf magazine IsNot BookandTryCastare three ways of the same decision;TryCastdoes the test and the cast in one step and is the one to prefer when the cast is needed afterwards.
Conversions the compiler refuses
The operators also reject the conversions that cannot possibly work. The next program is deliberately wrong and was built to show the compiler messages:
Option Strict On
Class Book
End Class
Class Magazine
End Class
Module Program
Sub Main()
Dim dune As New Book()
Dim viaFunction As Integer = CInt(dune)
Dim unrelated As Magazine = DirectCast(dune, Magazine)
Dim fromString As Book = CType("text", Book)
Dim valueTryCast As Integer = TryCast(5, Integer)
Dim nullableTryCast As Integer? = TryCast(dune, Integer?)
Dim directLong As Long = DirectCast(5, Long)
Dim asDate As Date = CDate(5)
Dim isInt As Boolean = TypeOf 5 Is Integer
End Sub
End Module
Program.vb(12,43): error BC30311: Value of type 'Book' cannot be converted to 'Integer'.
Program.vb(13,48): error BC30311: Value of type 'Book' cannot be converted to 'Magazine'.
Program.vb(14,40): error BC30311: Value of type 'String' cannot be converted to 'Book'.
Program.vb(15,50): error BC30792: 'TryCast' operand must be reference type, but 'Integer' is a value type.
Program.vb(16,57): error BC30792: 'TryCast' operand must be reference type, but 'Integer?' is a value type.
Program.vb(17,45): error BC30311: Value of type 'Integer' cannot be converted to 'Long'.
Program.vb(18,36): error BC30311: Value of type 'Integer' cannot be converted to 'Date'.
Program.vb(19,39): error BC30021: 'TypeOf ... Is' requires its left operand to have a reference type, but this operand has the value type 'Integer'.
Source: CType Operator
Two of these are surprising. DirectCast(5, Long) is BC30311 even though the same Integer widens to Long implicitly: DirectCast accepts only reference conversions and
unboxing, so the argument has to be an Object for the Long request to be legal (and then it fails unless the box holds a Long). And TryCast is limited to reference types: for a nullable or a number,
use TypeOf ... Is followed by DirectCast, or a parse method.
The CType of a numeric literal to an enumeration, to a type with a user-defined conversion and between related classes is where CType is the right tool; for numbers, prefer the conversion function whose name states the target, as it is
shorter and the rounding is the same.
Text, culture and Parse
Converting between text and values is where conversion bugs live, because the same text means different things in different cultures and because users type incorrect text. There are three families, with different defaults:
| Family | Culture | On bad input | Notes |
|---|---|---|---|
|
The current culture, except |
|
Short, forgiving ( |
|
The current culture, with an |
|
Rounds like |
|
The current culture unless you pass one. |
|
Explicit |
|
As |
Return |
The right choice for user input. |
The same code produces different results on two machines, which the next program shows by switching CultureInfo.CurrentCulture between en-US and de-DE and printing what each conversion
does with the same text:
Option Strict On
Imports System.Globalization
Module Program
Private Function Safe(action As Func(Of String)) As String
Try
Return action()
Catch ex As InvalidCastException
Return ex.GetType().Name
End Try
End Function
Private Sub Report(cultureName As String)
CultureInfo.CurrentCulture = New CultureInfo(cultureName)
Dim invariant As CultureInfo = CultureInfo.InvariantCulture
Dim price As Double = 1234.5
Dim parsed As Double
Console.WriteLine("[" & cultureName & "]")
Console.WriteLine(" CStr(1234.5) = " & CStr(price))
Console.WriteLine(" price.ToString(Invariant) = " & price.ToString(invariant))
Console.WriteLine(" CDbl(""1,234.5"") = " & Safe(Function() CDbl("1,234.5").ToString(invariant)))
Console.WriteLine(" CDbl(""1.234,5"") = " & Safe(Function() CDbl("1.234,5").ToString(invariant)))
Console.WriteLine(" Val(""1234.5abc"") = " & Val("1234.5abc").ToString(invariant))
Console.WriteLine(" CDate(""10/11/2026"") = " & CDate("10/11/2026").ToString("yyyy-MM-dd", invariant))
Console.WriteLine(" TryParse(""1,5"", Invariant) = " & Double.TryParse("1,5", NumberStyles.Float, invariant, parsed).ToString())
Console.WriteLine(" TryParse(""1,5"", current) = " & Double.TryParse("1,5", NumberStyles.Float, CultureInfo.CurrentCulture, parsed).ToString() & " " & parsed.ToString(invariant))
End Sub
Sub Main()
Report("en-US")
Report("de-DE")
End Sub
End Module
[en-US]
CStr(1234.5) = 1234.5
price.ToString(Invariant) = 1234.5
CDbl("1,234.5") = 1234.5
CDbl("1.234,5") = InvalidCastException
Val("1234.5abc") = 1234.5
CDate("10/11/2026") = 2026-10-11
TryParse("1,5", Invariant) = False
TryParse("1,5", current) = False 0
[de-DE]
CStr(1234.5) = 1234,5
price.ToString(Invariant) = 1234.5
CDbl("1,234.5") = InvalidCastException
CDbl("1.234,5") = 1234.5
Val("1234.5abc") = 1234.5
CDate("10/11/2026") = 2026-11-10
TryParse("1,5", Invariant) = False
TryParse("1,5", current) = True 1.5
Source: CultureInfo.InvariantCulture
Reading it:
-
CStr(1234.5)is1234.5inen-USand1234,5inde-DE. Text that is stored in a file or sent over a network must useToString(CultureInfo.InvariantCulture). -
CDbl("1,234.5")is accepted inen-US(the comma is a thousands separator) and rejected inde-DE;CDbl("1.234,5")is the mirror image. -
Valdoes not look at the culture: it reads digits and the period, stops at the first other character and returns aDouble, soVal("1234.5abc")is 1234.5 everywhere. It is also silent about bad input (Val("abc")is 0), which is why it has no place in validation. -
CDate("10/11/2026")is 11 October inen-USand 10 November inde-DE. Date text without a year-first format is ambiguous: storeyyyy-MM-dd, parse withDate.TryParseExact, and never rely onCDate. -
Double.TryParse("1,5", NumberStyles.Float, ...)isFalsewith the invariant culture andTrue(value 1.5) withde-DE.
The parse and Convert methods have more detailed behaviour, and the next program lists it for integers, Boolean, dates and enumerations:
Option Strict On
Imports System.Globalization
Enum LoanState
Available = 1
OnLoan = 2
Overdue = 3
End Enum
Module Program
Private Sub Attempt(label As String, action As Func(Of Object))
Try
Dim result As Object = action()
Console.WriteLine(label.PadRight(44) & TypeName(result) & " " & Convert.ToString(result, CultureInfo.InvariantCulture))
Catch ex As Exception
Console.WriteLine(label.PadRight(44) & ex.GetType().Name)
End Try
End Sub
Sub Main()
Dim invariant As CultureInfo = CultureInfo.InvariantCulture
Dim twoAndHalf As Double = 2.5
Dim threeAndHalf As Double = 3.5
Dim flag As Boolean = True
Dim missing As String = Nothing
Dim input As String = "1,234"
' Convert rounds like CInt; Boolean converts to 1, not -1.
Attempt("Convert.ToInt32(2.5) / (3.5)", Function() Convert.ToInt32(twoAndHalf).ToString() & " / " & Convert.ToInt32(threeAndHalf).ToString())
Attempt("CInt(True) and Convert.ToInt32(True)", Function() CInt(flag).ToString() & " and " & Convert.ToInt32(flag).ToString())
Attempt("Convert.ToBoolean(""True"")", Function() Convert.ToBoolean("True"))
Attempt("Convert.ToInt32(Nothing As String)", Function() Convert.ToInt32(missing))
Attempt("Integer.Parse(Nothing As String)", Function() Integer.Parse(missing))
Attempt("Convert.ToInt32(""12"", 16)", Function() Convert.ToInt32("12", 16))
' Parse and TryParse: culture and styles are explicit.
Attempt("Integer.Parse(""1,234"")", Function() Integer.Parse(input))
Attempt("Integer.Parse(""1,234"", AllowThousands)", Function() Integer.Parse(input, NumberStyles.AllowThousands, invariant))
Attempt("Integer.Parse(""abc"")", Function() Integer.Parse("abc", invariant))
Attempt("Integer.Parse(""3000000000"")", Function() Integer.Parse("3000000000", invariant))
Attempt("Integer.Parse("" 42 "")", Function() Integer.Parse(" 42 ", invariant))
Dim parsed As Integer
Dim ok As Boolean = Integer.TryParse("42abc", NumberStyles.Integer, invariant, parsed)
Console.WriteLine("TryParse(""42abc"")".PadRight(44) & ok.ToString() & ", parsed = " & parsed.ToString())
ok = Integer.TryParse("42", NumberStyles.Integer, invariant, parsed)
Console.WriteLine("TryParse(""42"")".PadRight(44) & ok.ToString() & ", parsed = " & parsed.ToString())
Dim moment As Date
ok = Date.TryParseExact("2026-10-19", "yyyy-MM-dd", invariant, DateTimeStyles.None, moment)
Console.WriteLine("Date.TryParseExact(yyyy-MM-dd)".PadRight(44) & ok.ToString() & ", " & moment.ToString("dddd d MMMM yyyy", invariant))
' Enumerations: a number converts without any check that it names a member.
Dim state As LoanState = CType(2, LoanState)
Dim unknown As LoanState = CType(9, LoanState)
Console.WriteLine("CType(2, LoanState) / CType(9, ...)".PadRight(44) & state.ToString() & " / " & unknown.ToString() & ", IsDefined(9) = " & [Enum].IsDefined(GetType(LoanState), 9).ToString())
Dim byName As LoanState
ok = [Enum].TryParse("overdue", True, byName)
Console.WriteLine("Enum.TryParse(""overdue"", ignoreCase)".PadRight(44) & ok.ToString() & ", " & byName.ToString() & " = " & CInt(byName).ToString())
Attempt("[Enum].Parse(GetType(LoanState), ""Lost"")", Function() [Enum].Parse(GetType(LoanState), "Lost"))
End Sub
End Module
Convert.ToInt32(2.5) / (3.5) String 2 / 4
CInt(True) and Convert.ToInt32(True) String -1 and 1
Convert.ToBoolean("True") Boolean True
Convert.ToInt32(Nothing As String) Integer 0
Integer.Parse(Nothing As String) ArgumentNullException
Convert.ToInt32("12", 16) Integer 18
Integer.Parse("1,234") FormatException
Integer.Parse("1,234", AllowThousands) Integer 1234
Integer.Parse("abc") FormatException
Integer.Parse("3000000000") OverflowException
Integer.Parse(" 42 ") Integer 42
TryParse("42abc") False, parsed = 0
TryParse("42") True, parsed = 42
Date.TryParseExact(yyyy-MM-dd) True, Monday 19 October 2026
CType(2, LoanState) / CType(9, ...) OnLoan / 9, IsDefined(9) = False
Enum.TryParse("overdue", ignoreCase) True, Overdue = 3
[Enum].Parse(GetType(LoanState), "Lost") ArgumentException
Source: Int32.TryParse
-
Convert.ToInt32(2.5)is 2 andConvert.ToInt32(3.5)is 4, the same banker’s rounding asCInt, butConvert.ToInt32(True)is 1 whereCInt(True)is -1. -
Convert.ToInt32(Nothing As String)is 0 andInteger.Parse(Nothing As String)throwsArgumentNullException.Convert.ToInt32("12", 16)is 18: the second argument is the base. -
Integer.Parse("1,234")fails withFormatExceptionbecause the default styleNumberStyles.Integerhas no thousands separator; withNumberStyles.AllowThousandsit gives 1234. The default style does allow leading and trailing white space (" 42 "is 42). -
Integer.Parse("3000000000")isOverflowException, notFormatException: the text is a valid number that does not fit. -
TryParse("42abc")isFalsewith the result 0, andTryParse("42")isTrue. Always test the returnedBoolean. -
Date.TryParseExactwith an explicit format andCultureInfo.InvariantCultureis the one safe way to read a date from a file. -
An enumeration accepts any integer:
CType(9, LoanState)is aLoanStatethat prints as9, because no member has that value.[Enum].IsDefinedtells you;[Enum].TryParsereads names and numbers, optionally ignoring case, and[Enum].ParsethrowsArgumentExceptionfor a name that does not exist.
User-defined conversions
A Structure or a Class can define conversions to and from other types with the Widening and Narrowing operator forms. A Widening conversion is implicit and must never
fail or lose information; a Narrowing conversion has to be written with CType (or a conversion function, with Option Strict On) and may throw. The following structure is the shelf’s ISBN, with the
two forms. Every Isbn is a valid String, so conversion to String is widening. Not every String is an Isbn, so the conversion from String validates, throws and is narrowing:
Option Strict On
Public Structure Isbn
Implements IEquatable(Of Isbn)
Public ReadOnly Property Value As String
Private Sub New(value As String)
Me.Value = value
End Sub
Public Shared Function TryParse(text As String, ByRef result As Isbn) As Boolean
Dim digits As String = If(text, "").Replace("-", "")
If digits.Length = 13 AndAlso digits.All(AddressOf Char.IsDigit) AndAlso HasValidCheckDigit(digits) Then
result = New Isbn(digits)
Return True
End If
Return False
End Function
Private Shared Function HasValidCheckDigit(digits As String) As Boolean
Dim sum As Integer = 0
For i As Integer = 0 To 12
Dim digit As Integer = AscW(digits(i)) - AscW("0"c)
sum += If(i Mod 2 = 0, digit, digit * 3)
Next
Return sum Mod 10 = 0
End Function
' Every Isbn is a valid String, so this conversion can never fail and may be implicit.
Public Shared Widening Operator CType(isbn As Isbn) As String
Return isbn.Value
End Operator
' Not every String is an Isbn: the conversion validates, throws, and must be written explicitly.
Public Shared Narrowing Operator CType(text As String) As Isbn
Dim result As Isbn = Nothing
If Not TryParse(text, result) Then
Throw New InvalidCastException("'" & text & "' is not a valid ISBN-13.")
End If
Return result
End Operator
Public Overloads Function Equals(other As Isbn) As Boolean Implements IEquatable(Of Isbn).Equals
Return Value = other.Value
End Function
Public Overrides Function ToString() As String
Return Value
End Function
End Structure
Module Program
Private Function Shout(text As String) As String
Return "[" & text.ToUpperInvariant() & "]"
End Function
Sub Main()
Dim isbn As Isbn = CType("978-0-441-01359-3", Isbn)
Dim asText As String = isbn
Console.WriteLine("widening, no cast needed : " & asText & " (" & asText.Length.ToString() & " characters)")
Console.WriteLine("as a String argument : " & Shout(isbn))
Try
Dim bad As Isbn = CType("978-0-441-01359-4", Isbn)
Console.WriteLine(bad)
Catch ex As InvalidCastException
Console.WriteLine("narrowing, bad check digit: " & ex.Message)
End Try
' The conversion operator is chosen at compile time from the static types. DirectCast never uses it,
' and CType on an Object only unboxes: a String inside an Object is not turned into an Isbn.
Dim boxed As Object = isbn
Console.WriteLine("DirectCast(boxed, Isbn) : " & DirectCast(boxed, Isbn).Value)
Dim hidden As Object = "9780441013593"
Try
Console.WriteLine(DirectCast(hidden, Isbn))
Catch ex As InvalidCastException
Console.WriteLine("DirectCast of an Object String: " & ex.GetType().Name)
End Try
Try
Console.WriteLine(CType(hidden, Isbn))
Catch ex As InvalidCastException
Console.WriteLine("CType of an Object String : " & ex.GetType().Name)
End Try
Console.WriteLine("CType of a typed String : " & CType(DirectCast(hidden, String), Isbn).Value)
End Sub
End Module
widening, no cast needed : 9780441013593 (13 characters)
as a String argument : [9780441013593]
narrowing, bad check digit: '978-0-441-01359-4' is not a valid ISBN-13.
DirectCast(boxed, Isbn) : 9780441013593
DirectCast of an Object String: InvalidCastException
CType of an Object String : InvalidCastException
CType of a typed String : 9780441013593
Source: Operator Statement
The rules that follow:
-
The conversion operator is
Public Shared Widening|Narrowing Operator CType(parameter As From) As To, and eitherFromorTohas to be the type that declares it. -
A conversion cannot be defined between a type and its base type or an interface it implements, nor from or to
Object; those exist already as reference conversions. -
The compiler chooses the operator from the static types at compile time. A
Stringthat arrives inside anObjectis not converted: the last two attempts of the program,DirectCast(hidden, Isbn)andCType(hidden, Isbn), both throwInvalidCastExceptionalthoughCTypeof a typedStringworks.DirectCastandTryCastnever use user-defined conversions. -
An implicit widening conversion is applied to assignments and arguments (
Dim asText As String = isbnandShout(isbn)). It was not applied to the operand of&in the experiment:"ISBN " & isbnfails, as the next listing shows, and a member of the target type is not visible on the source (isbn.Length). -
A narrowing conversion whose failure is an exceptional case should throw
InvalidCastException; callers that expect failure need aTryParsemethod next to it, asIsbn.TryParseis.
The next file is deliberately wrong and shows the compiler’s answer for each misuse:
Option Strict On
Public Structure Isbn
Implements IEquatable(Of Isbn)
Public ReadOnly Property Value As String
Private Sub New(value As String)
Me.Value = value
End Sub
Public Shared Function TryParse(text As String, ByRef result As Isbn) As Boolean
Dim digits As String = If(text, "").Replace("-", "")
If digits.Length = 13 AndAlso digits.All(AddressOf Char.IsDigit) AndAlso HasValidCheckDigit(digits) Then
result = New Isbn(digits)
Return True
End If
Return False
End Function
Private Shared Function HasValidCheckDigit(digits As String) As Boolean
Dim sum As Integer = 0
For i As Integer = 0 To 12
Dim digit As Integer = AscW(digits(i)) - AscW("0"c)
sum += If(i Mod 2 = 0, digit, digit * 3)
Next
Return sum Mod 10 = 0
End Function
' Every Isbn is a valid String, so this conversion can never fail and may be implicit.
Public Shared Widening Operator CType(isbn As Isbn) As String
Return isbn.Value
End Operator
' Not every String is an Isbn: the conversion validates, throws, and must be written explicitly.
Public Shared Narrowing Operator CType(text As String) As Isbn
Dim result As Isbn = Nothing
If Not TryParse(text, result) Then
Throw New InvalidCastException("'" & text & "' is not a valid ISBN-13.")
End If
Return result
End Operator
Public Overloads Function Equals(other As Isbn) As Boolean Implements IEquatable(Of Isbn).Equals
Return Value = other.Value
End Function
Public Overrides Function ToString() As String
Return Value
End Function
End Structure
Module Program
Sub Main()
Dim isbn As Isbn = CType("978-0-441-01359-3", Isbn)
Dim implicitNarrowing As Isbn = "9780441013593"
Dim concatenated As String = "ISBN " & isbn
Dim direct As Isbn = DirectCast("9780441013593", Isbn)
Dim tried As Isbn = TryCast("9780441013593", Isbn)
Dim length As Integer = isbn.Length
End Sub
End Module
Program.vb(56,41): error BC30512: Option Strict On disallows implicit conversions from 'String' to 'Isbn'.
Program.vb(57,38): error BC30452: Operator '&' is not defined for types 'String' and 'Isbn'.
Program.vb(58,41): error BC30311: Value of type 'String' cannot be converted to 'Isbn'.
Program.vb(59,54): error BC30792: 'TryCast' operand must be reference type, but 'Isbn' is a value type.
Program.vb(60,33): error BC30456: 'Length' is not a member of 'Isbn'.
Source: Narrowing
The listing shows Option Strict On disallowing the implicit narrowing at line 56 (BC30512), the missing operator & for String and Isbn at line 57 (BC30452), the refusal of DirectCast at line 58 (BC30311), TryCast
on a value type at line 59 (BC30792) and Length which Isbn does not have at line 60 (BC30456). Learn describes Widening as the modifier of an operator that converts to a type that
can always hold the value, and the rules for the combinations in the page for the Operator statement.
Arrays, generics and delegates: variance
Variance is the rule that lets a Book collection stand in for a CatalogItem collection. Visual Basic supports it for arrays, for generic interfaces and delegates that declare it
(since Visual Basic 2010), and for delegate parameter types.
Option Strict On
Imports System.Collections.Generic
Public MustInherit Class CatalogItem
Public ReadOnly Property Title As String
Protected Sub New(title As String)
Me.Title = title
End Sub
End Class
Public Class Book
Inherits CatalogItem
Public Sub New(title As String)
MyBase.New(title)
End Sub
End Class
Public Class Magazine
Inherits CatalogItem
Public Sub New(title As String)
MyBase.New(title)
End Sub
End Class
Module Program
Sub Main()
Dim books As Book() = {New Book("Dune"), New Book("Emma")}
' Array covariance: an array of Book is an array of CatalogItem, checked at run time on store.
Dim items As CatalogItem() = books
Console.WriteLine("array covariance: " & items.Length.ToString() & " items, runtime type " & items.GetType().Name)
Try
items(0) = New Magazine("Wired")
Catch ex As ArrayTypeMismatchException
Console.WriteLine("storing a Magazine in the Book array: " & ex.GetType().Name)
End Try
' Generic covariance: IEnumerable(Of Out T) and read-only interfaces allow the conversion implicitly.
Dim readOnlyItems As IEnumerable(Of CatalogItem) = books
Console.WriteLine("IEnumerable(Of Book) -> IEnumerable(Of CatalogItem): " & readOnlyItems.Count().ToString())
Dim list As New List(Of Book)(books)
Dim readOnlyList As IReadOnlyList(Of CatalogItem) = list
Console.WriteLine("List(Of Book) -> IReadOnlyList(Of CatalogItem): " & readOnlyList.Count.ToString())
' Dim itemList As List(Of CatalogItem) = list ' error BC30512: List(Of T) is invariant
' Delegate contravariance: a method that accepts any CatalogItem can serve where a Book is supplied.
Dim describeItem As Func(Of CatalogItem, String) = Function(i) "item " & i.Title
Dim describeBook As Func(Of Book, String) = describeItem
Console.WriteLine("Func(Of CatalogItem, String) -> Func(Of Book, String): " & describeBook(books(1)))
Dim makeBook As Func(Of Book) = Function() New Book("Ulysses")
Dim makeItem As Func(Of CatalogItem) = makeBook
Console.WriteLine("Func(Of Book) -> Func(Of CatalogItem): " & makeItem().Title)
' Booleans and numbers.
Dim hasCopies As Boolean = CBool(3)
Console.WriteLine("CBool(3) = " & hasCopies.ToString() & ", CInt(True) = " & CInt(hasCopies).ToString() & ", Math.Abs(CInt(True)) = " & Math.Abs(CInt(hasCopies)).ToString())
' An array of one type is not an array of another: element conversion needs a loop or LINQ.
Dim numbers As Integer() = {1, 2, 3}
Dim doubles As Double() = Array.ConvertAll(numbers, Function(n) CDbl(n))
Console.WriteLine("Array.ConvertAll: " & String.Join(", ", doubles))
End Sub
End Module
array covariance: 2 items, runtime type Book[]
storing a Magazine in the Book array: ArrayTypeMismatchException
IEnumerable(Of Book) -> IEnumerable(Of CatalogItem): 2
List(Of Book) -> IReadOnlyList(Of CatalogItem): 2
Func(Of CatalogItem, String) -> Func(Of Book, String): item Emma
Func(Of Book) -> Func(Of CatalogItem): Ulysses
CBool(3) = True, CInt(True) = -1, Math.Abs(CInt(True)) = 1
Array.ConvertAll: 1, 2, 3
-
Arrays are covariant: a
Book()converts implicitly toCatalogItem(). The conversion is unsafe, because the array keeps its real type and a store of the wrong element throwsArrayTypeMismatchException, as the second line shows. Arrays of value types are not covariant (see the errors below). -
Generic interfaces declare variance with
Out(the type only comes out, soIEnumerable(Of Book)is anIEnumerable(Of CatalogItem)) andIn(the type only goes in).IReadOnlyList(Of Out T)andIEnumerable(Of Out T)are covariant;List(Of T),ICollection(Of T)andIList(Of T)are invariant, because they both read and write. -
Delegates are contravariant in their parameters and covariant in their result: a
Func(Of CatalogItem, String)is aFunc(Of Book, String), and aFunc(Of Book)is aFunc(Of CatalogItem). TheInandOutofFunc(Of In T, Out TResult)appear in the error message below. -
There is no conversion between arrays of unrelated element types.
Array.ConvertAll(orSelect…ToArray) creates the new array.
The next file is wrong on purpose, and the messages show where variance stops:
Option Strict On
Imports System.Collections.Generic
Class CatalogItem
Public Title As String = ""
End Class
Class Book
Inherits CatalogItem
End Class
Class Magazine
Inherits CatalogItem
End Class
Module Program
Sub Main()
Dim list As New List(Of Book)()
Dim itemList As List(Of CatalogItem) = list
Dim numbers As Integer() = {1, 2, 3}
Dim doubles As Double() = numbers
Dim bookFromItem As Book = New Magazine()
Dim describeBook As Func(Of Book, String) = Function(b) b.Title
Dim describeItem As Func(Of CatalogItem, String) = describeBook
End Sub
End Module
Program.vb(20,48): error BC36756: 'List(Of Book)' cannot be converted to 'List(Of CatalogItem)'. Consider using 'IEnumerable(Of CatalogItem)' instead.
Program.vb(22,35): error BC30332: Value of type 'Integer()' cannot be converted to 'Double()' because 'Integer' is not derived from 'Double'.
Program.vb(23,36): error BC30311: Value of type 'Magazine' cannot be converted to 'Book'.
Program.vb(25,60): error BC36755: 'Func(Of Book, String)' cannot be converted to 'Func(Of CatalogItem, String)' because 'CatalogItem' is not derived from 'Book', as required for the 'In' generic parameter 'T' in 'Delegate Function Func(Of In T, Out TResult)(arg As T) As TResult'.
Source: Array Conversions
BC36756 has the helpful hint "Consider using 'IEnumerable(Of CatalogItem)' instead": List(Of Book) is not a List(Of CatalogItem). BC30332 is the
array of value types, where Integer() to Double() does not exist even though each element converts. BC36755 is the delegate that would be unsafe, a Func(Of Book, String) offered where it
must accept every CatalogItem. Delegate conversion without arguments (AddressOf of a method with fewer parameters) is relaxed only in some contexts; with Option Strict On, see
Program Structure and Code Conventions for the Handles case and BC36663.
Boolean and enumerations
-
CInt(True)is -1 andCInt(False)is 0 because Visual Basic representsTrueas all bits set. Every non-zero number converts toTrue.Convert.ToInt32(True)is 1, andTruestored in a database or sent to another language is a source of off-by-sign errors. WriteIf(flag, 1, 0)when you need the number 1. -
A
Booleanwidens only toObject. To a number or toStringit is a narrowing conversion (and the reverse), soOption Strict OnrequiresCInt,CBool,CStrand the like; toCharandDateno conversion exists (BC30311). In the shelf program,CBool(3)isTrueandMath.Abs(CInt(True))is 1. -
An
Enumconverts to and from its underlying integral type explicitly (CInt(state),CType(2, LoanState)) and never checks that the number names a member.Enumvalues also convert toStringwithToStringand fromStringwith[Enum].ParseorTryParse.
Which conversion should I use?
The diagram follows the decisions of the previous sections:
to be read as a number,
date or Boolean?"} q1 -- yes --> q2{"Can the text be wrong,
for example user input?"} q2 -- yes --> tp["TryParse or TryParseExact
with an explicit CultureInfo
and NumberStyles"] q2 -- no --> q3{"Is the format fixed
and the same on every machine?"} q3 -- yes --> pa["Integer.Parse or Double.Parse
with CultureInfo.InvariantCulture"] q3 -- no --> cf["CInt, CDbl or CDate
read the current culture:
only for text of that culture"] q1 -- no --> q4{"Are both types
numeric or Boolean or Char
or Date?"} q4 -- yes --> q5{"Is it a widening conversion
in the matrix?"} q5 -- yes --> im["Assign directly:
the conversion is implicit"] q5 -- no --> fn["Conversion function CInt, CLng, CDbl...
round first with Math.Round or Fix
if you need another rule"] q4 -- no --> q6{"Are the types related by
inheritance or an interface?"} q6 -- "up to base or interface" --> im q6 -- "down to a subtype" --> q7{"Might the object
be another type?"} q7 -- yes --> tc["TryCast and test for Nothing,
or TypeOf ... Is before DirectCast"] q7 -- no --> dc["DirectCast: exact,
throws InvalidCastException
when it is wrong"] q6 -- no --> q8{"Does one of the types define
a Widening or Narrowing operator?"} q8 -- yes --> ct["CType with both static types known"] q8 -- no --> q9{"Is the source an Object
that holds a boxed value?"} q9 -- "exact type known" --> dc q9 -- "any convertible type" --> co["CInt, CType or Convert
convert at run time"] q9 -- no --> none["No conversion exists:
write a method or a mapping"]
Source: Type Conversions
The exceptions
| Exception | Raised by | Remedy |
|---|---|---|
|
|
Test with |
|
A conversion or arithmetic result outside the target range; |
Check the range, widen first ( |
|
|
|
|
|
Test for |
|
Storing an element into an array that is covariantly typed as a base array. |
Prefer |
Source: InvalidCastException
Differences from C#
| Concern | Visual Basic | C# |
|---|---|---|
Implicit narrowing |
Error under |
Always an error. |
Cast of a floating-point number to an integer |
|
|
Overflow |
Checked by default: |
|
Reference downcast |
|
|
Type test |
|
|
Conversion of an |
|
|
String to number |
|
|
|
|
No implicit or explicit conversion. |
User-defined conversions |
|
|
Type of |
|
Integer division for integers. |
Generic variance |
|
|
Where the books are outdated or wrong
-
Mastering VB.NET p. 43 assigns
a / banda ^ btoIntegervariables: both operators returnDouble, and the assignments do not compile withOption Strict On(BC30512, as the matrix section shows). Writea \ b, orCInt(a / b)when rounding is the intent, andCInt(a ^ b). -
Mastering VB.NET pp. 54-55, 119-121 and 128 say that
10 / 0throwsDivideByZeroException./on integer operands converts toDoubleand givesInfinity; only\,ModandDecimaldivision throw (the numbers section of Data Types and Literals runs them). -
Quickstart Guide pp. 18 and 134 use
+to concatenate strings, which the first section of this page shows is anOption Strict Onerror and a numeric addition in disguise otherwise. Use&. -
Quickstart Guide p. 80 presents
DateValueas if its result did not depend on the culture. It does, likeCDate, as the culture example shows; useDate.TryParseExactwith the invariant culture for stored dates. -
Quickstart Guide pp. 83-84 give
FormatDateTimecustom format strings. It takes only the named formats; custom formats belong toToString(format). The page on Dates, Times and Formatting covers both.