Strings and Text
|
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. |
Text is where Visual Basic shows its two layers most clearly. The first layer is the .NET String type: an immutable sequence of UTF-16 code
units, with instance methods that count from zero, exactly as in C#. The second layer is the Microsoft.VisualBasic.Strings module, inherited from
Visual Basic 6 and imported into every file by default: functions such as Mid, Left, InStr and Trim that count from one,
treat Nothing as the empty string and, for some of them, take their comparison mode from Option Compare. Both layers are always available, and a large
part of writing good VB text code is knowing which one to use and where their rules differ. This page covers the types, the literals, the two sets of
operations, comparison and culture, building strings, regular expressions, encodings and the & versus + question. It was written against Visual Basic
17.13 on .NET 10 (SDK 10.0.402) and checked on 2026-10-19. Every program was built with Option Strict On and TreatWarningsAsErrors, and run in a Linux container, where
the current culture is the invariant culture; a program that depends on the current culture sets the culture it needs explicitly. For C#'s view of the same
material see Strings and Text in the C# reference; the formatting of numbers and dates is on
Dates, Times and Formatting, and the numeric conversions on
Type Conversions.
The String and Char types
String is the keyword for System.String: a reference type whose value is a sequence of UTF-16 code units (Char values), not of "characters" as a
person counts them. A String is immutable, so every operation that seems to change one returns a new string, and a string literal is the only form of
constant that can be a reference type. Nothing is a valid value of type String (see
Nothing and Nullable Value Types), and the VB operators and Strings
functions treat it like "".
Char is the keyword for System.Char, a 16-bit UTF-16 code unit. There is no implicit conversion from a one-character String to Char: the declaration
Dim c As Char = "A" is BC30512 under Option Strict On ("disallows implicit conversions from String to Char`"). A `Char literal is a string literal with the
suffix c: "A"c. A string is indexed from zero: s(0) and s.Chars(0) are the same read-only indexer, and the compiler rejects an assignment to it with
BC30526 ("Property Chars is `ReadOnly`").
Unicode: code units, runes and text elements
Because the unit is the UTF-16 code unit, three notions of "length" coexist. Length counts code units. Rune (System.Text, .NET Core 3.0 and later) is a Unicode scalar value,
and one rune takes one or two code units (a surrogate pair). A grapheme cluster, what a reader sees as one character, may consist of several runes, for example a letter followed by a
combining accent, and System.Globalization.StringInfo enumerates those "text elements".
Option Strict On
Imports System.Globalization
Imports System.Text
Module Ex02
Sub Main()
Console.OutputEncoding = Encoding.UTF8
Dim s = "Shelf"
Dim t = s.ToUpperInvariant()
Console.WriteLine($"{s} -> {t}; the original is unchanged: {s}")
Dim thumbsUp = ChrW(&HD83D) & ChrW(&HDC4D)
Dim accented = "e" & ChrW(&H301)
Console.WriteLine($"thumbs-up: Length = {thumbsUp.Length}, runes = {thumbsUp.EnumerateRunes().Count()}, first rune = U+{thumbsUp.EnumerateRunes().First().Value:X}")
Console.WriteLine($"surrogates: {Char.IsHighSurrogate(thumbsUp(0))} {Char.IsLowSurrogate(thumbsUp(1))}")
Console.WriteLine($"e + combining acute: Length = {accented.Length}, text elements = {New StringInfo(accented).LengthInTextElements}")
Console.WriteLine($"Normalized to FC: Length = {accented.Normalize(NormalizationForm.FormC).Length}")
Console.WriteLine($"Equal without normalization: {accented = ChrW(&HE9)}")
Console.WriteLine($"Equal after normalization: {accented.Normalize() = ChrW(&HE9)}")
Dim c As Char = "7"c
Console.WriteLine($"IsDigit={Char.IsDigit(c)}, IsLetter={Char.IsLetter(c)}, GetNumericValue={Char.GetNumericValue(c)}")
Console.WriteLine($"ToUpperInvariant(""ß"") = {"ß".ToUpperInvariant()}, Char.ToUpperInvariant('ß') = {Char.ToUpperInvariant("ß"c)}")
Dim reversed = New String(s.Reverse().ToArray())
Console.WriteLine($"Reversed with LINQ: {reversed}; with StrReverse: {StrReverse(s)}")
Dim mixed = "a" & thumbsUp & accented
Dim byUnits = New String(mixed.Reverse().ToArray())
Console.WriteLine($"Reverse by code units : {String.Join(" ", byUnits.Select(Function(ch) AscW(ch).ToString("X4")))}")
Console.WriteLine($"StrReverse : {String.Join(" ", StrReverse(mixed).Select(Function(ch) AscW(ch).ToString("X4")))}")
Console.WriteLine($"Chars: {String.Join(" ", s.Select(Function(ch) AscW(ch)))}")
Console.WriteLine($"s(0) = {s(0)}, s.Chars(1) = {s.Chars(1)}")
End Sub
End Module
Shelf -> SHELF; the original is unchanged: Shelf
thumbs-up: Length = 2, runes = 1, first rune = U+1F44D
surrogates: True True
e + combining acute: Length = 2, text elements = 1
Normalized to FC: Length = 1
Equal without normalization: False
Equal after normalization: True
IsDigit=True, IsLetter=False, GetNumericValue=7
ToUpperInvariant("ß") = ß, Char.ToUpperInvariant('ß') = ß
Reversed with LINQ: flehS; with StrReverse: flehS
Reverse by code units : 0301 0065 DC4D D83D 0061
StrReverse : 0065 0301 D83D DC4D 0061
Chars: 83 104 101 108 102
s(0) = S, s.Chars(1) = h
Source: Character encoding in .NET
Consequences you should plan for: Length and Mid/Substring can cut a surrogate pair in half; LINQ’s Reverse() reverses code units and breaks pairs and combining
sequences (StrReverse, in contrast, reverses by text element and keeps them intact); Equals and = compare code units, so a precomposed é and e + U+0301 are different strings until both are normalised with Normalize();
ToUpperInvariant() and Char.ToUpperInvariant apply simple one-to-one mappings (the ß stays ß). For text that users see, process runes or text elements, or leave the splitting to a library.
String literals and special characters
A string literal is text between double quotes. There are no backslash escape sequences (so Windows paths and regular expressions can be written as is), a
double quote inside the literal is written twice (""), and nothing else is special. Visual Basic 14 (Visual Studio 2015) allows a string literal to continue over several source lines, and
the literal then contains the line break that is in the source file: LF in a file saved with Unix line endings (length 12 in the program below), CRLF in a file saved with Windows line endings. A literal whose content depends on how
the file is saved is a portability bug in disguise; build line breaks explicitly with vbCrLf, vbLf or Environment.NewLine unless the line ending is irrelevant.
Characters that cannot be typed are named with constants and functions: ControlChars.Tab, ControlChars.Cr, ControlChars.Lf, ControlChars.CrLf,
ControlChars.NewLine (always CR LF when measured on Linux with .NET 10, unlike Environment.NewLine, which is LF there), ControlChars.Quote, ControlChars.NullChar, ControlChars.Back, ControlChars.FormFeed, ControlChars.VerticalTab, the VB6-era
constants vbTab, vbCr, vbLf, vbCrLf (always CR LF, two characters), vbNewLine (the platform’s), vbNullChar, vbBack, vbFormFeed, vbVerticalTab, vbNullString (which is simply Nothing), and ChrW(code) / AscW(text)
for any UTF-16 code unit. Source files may contain any Unicode text, so a literal "€" is fine. The compiler also accepts the typographic quotes U+201C and U+201D as string delimiters (a program that wrote “smart”
compiled and printed smart), a behaviour that surprises people who paste code from word processors: an unnoticed pair of curly quotes is a legal string literal.
Option Strict On
Module Ex01
Sub Main()
' Quotes are doubled; there are no backslash escapes.
Dim quoted = "She said ""Hello"" and left"
Dim windowsPath = "C:\Shelf\catalog.json"
Console.WriteLine(quoted)
Console.WriteLine(windowsPath)
' Special characters come from constants, ControlChars or ChrW.
Console.WriteLine("Tab:" & vbTab & "|" & ControlChars.Tab & "|")
Console.WriteLine("Two lines:" & Environment.NewLine & "second line")
Console.WriteLine("Euro sign: " & ChrW(&H20AC) & ", code " & AscW("€"c))
Console.WriteLine("vbCrLf length = " & vbCrLf.Length & ", vbLf length = " & vbLf.Length)
' A string literal may contain line breaks (VB 14): the text includes the file's own line endings.
Dim poem = "first
second"
Console.WriteLine("Literal with a line break has length " & poem.Length & " and contains LF: " & poem.Contains(vbLf))
' Character literals use the c suffix.
Dim letter As Char = "A"c
Console.WriteLine(letter & " has code " & AscW(letter) & ", next is " & ChrW(AscW(letter) + 1))
' Interpolation: {expression,alignment:format}
Dim title = "Dune"
Dim year = 1965
Dim price = 12.5D
Console.WriteLine($"[{title,-8}] [{title,8}] {year} {price:N2}")
Console.WriteLine($"Braces are doubled: {{{title}}}")
Console.WriteLine($"Nested quotes are fine: {If(year > 1900, "modern", "old")}")
Console.WriteLine($"Inside {title.ToUpperInvariant()} the calculation is {year + 1}")
End Sub
End Module
She said "Hello" and left
C:\Shelf\catalog.json
Tab: | |
Two lines:
second line
Euro sign: €, code 8364
vbCrLf length = 2, vbLf length = 1
Literal with a line break has length 12 and contains LF: True
A has code 65, next is B
[Dune ] [ Dune] 1965 12.50
Braces are doubled: {Dune}
Nested quotes are fine: modern
Inside DUNE the calculation is 1966
Source: Strings (Visual Basic)
What VB does not have, unlike C#: no \n or € escape sequences, no raw string literals (C# 11), no UTF-8 string literals ("abc"u8, C# 11: BC30205 "End of statement expected"), no
verbatim prefix (everything is already verbatim). Use ChrW, ControlChars and &.
Interpolated strings
Visual Basic 14 added interpolated strings: $"Hello {name}". A hole is {expression}, {expression,alignment}, {expression:format} or {expression,alignment:format}; the alignment is a signed
integer (negative left-aligns) and the format is any format string that the value’s type understands (see
Dates, Times and Formatting). {{ and }} produce literal braces. The expression is ordinary VB, including string literals with plain quotes and calls ({If(year > 1900, "modern", "old")}),
and the compiler turns the string into a call to String.Format, so the current culture applies to every formatted value. Assign it to FormattableString to defer the formatting and pass a culture later:
FormattableString.Invariant($"{price:N2}") is the idiom for stable, culture-independent output. VB 17.13 has no interpolated string handlers (a C# 10 feature):
String.Create(CultureInfo.InvariantCulture, $"{x}") fails with BC30311 ("Value of type String cannot be converted to DefaultInterpolatedStringHandler`"), so use `FormattableString.Invariant. VB has no $@ prefix and no multi-quote variants, and a
format specifier cannot contain }.
String methods and the Strings module
Every VB program can call both text.Substring(6, 5) and Mid(text, 7, 5). The module Microsoft.VisualBasic.Strings (documented in the Language Reference as the "string functions") is in scope without an Imports, because
Visual Basic projects import Microsoft.VisualBasic implicitly. The functions are thin wrappers, but they carry the VB6 semantics, and the differences are what matter.
Option Strict On
Module Ex03
Sub Main()
Dim text = "Hello World!"
Console.WriteLine("-- Mid is 1-based, Substring is 0-based")
Console.WriteLine($"Mid(text, 6, 5) = '{Mid(text, 6, 5)}'")
Console.WriteLine($"text.Substring(6, 5) = '{text.Substring(6, 5)}'")
Console.WriteLine($"Mid(text, 7, 5) = '{Mid(text, 7, 5)}'")
Console.WriteLine($"Mid(text, 7) = '{Mid(text, 7)}'")
Console.WriteLine($"Left(text, 5) = '{Left(text, 5)}'")
Console.WriteLine($"Right(text, 6) = '{Right(text, 6)}'")
Console.WriteLine($"Left(text, 99) = '{Left(text, 99)}'")
Console.WriteLine($"Mid(text, 99) = '{Mid(text, 99)}'")
Try
Console.WriteLine(text.Substring(99))
Catch ex As ArgumentOutOfRangeException
Console.WriteLine("text.Substring(99) throws " & ex.GetType().Name)
End Try
Try
Console.WriteLine(Mid(text, 0, 2))
Catch ex As ArgumentException
Console.WriteLine("Mid(text, 0, 2) throws " & ex.GetType().Name)
End Try
Console.WriteLine("-- Searching: 0 means not found for InStr, -1 for IndexOf")
Console.WriteLine($"InStr(text, ""o"") = {InStr(text, "o")}")
Console.WriteLine($"InStr(6, text, ""o"") = {InStr(6, text, "o")}")
Console.WriteLine($"InStrRev(text, ""o"") = {InStrRev(text, "o")}")
Console.WriteLine($"InStr(text, ""z"") = {InStr(text, "z")}")
Console.WriteLine($"text.IndexOf(""o"") = {text.IndexOf("o")}")
Console.WriteLine($"text.IndexOf(""z"") = {text.IndexOf("z")}")
Console.WriteLine($"text.LastIndexOf(""o"") = {text.LastIndexOf("o")}")
Console.WriteLine($"text.Contains(""World"") = {text.Contains("World")}")
Console.WriteLine("-- Trim: the VB function removes only spaces")
Dim padded = vbTab & " Shelf " & vbTab
Console.WriteLine($"Trim(padded).Length = {Trim(padded).Length}")
Console.WriteLine($"padded.Trim().Length = {padded.Trim().Length}")
Console.WriteLine($"LTrim / RTrim lengths = {LTrim(padded).Length} / {RTrim(padded).Length}")
Console.WriteLine("-- Case, length, repeat")
Console.WriteLine($"UCase / LCase = {UCase(text)} / {LCase(text)}")
Console.WriteLine($"Len(text) = {Len(text)}")
Console.WriteLine($"Space(3) & StrDup = '{Space(3)}' '{StrDup(3, "ab")}'")
Console.WriteLine($"ToTitleCase = {System.Globalization.CultureInfo.InvariantCulture.TextInfo.ToTitleCase("the lord of the rings")}")
Console.WriteLine("-- Replace, Split, Join")
Console.WriteLine($"Replace(text, ""l"", ""L"") = {Replace(text, "l", "L")}")
Console.WriteLine($"Replace(text, ""l"", ""L"", 5) = {Replace(text, "l", "L", 5)}")
Console.WriteLine($"Replace(text, ""l"", ""L"", 1, 2) = {Replace(text, "l", "L", 1, 2)}")
Console.WriteLine($"text.Replace(""l"", ""L"") = {text.Replace("l", "L")}")
Dim parts = Split("a,b,,c", ",")
Console.WriteLine($"Split keeps empty entries: {parts.Length} -> {Join(parts, "|")}")
Dim parts2 = "a,b,,c".Split(","c, StringSplitOptions.RemoveEmptyEntries)
Console.WriteLine($"String.Split with options: {parts2.Length} -> {String.Join("|", parts2)}")
Console.WriteLine($"Split(""a b c"") default delimiter is a space: {Split("a b c").Length}")
Console.WriteLine($"Split with limit: {Join(Split("a,b,c,d", ",", 2), "|")}")
Console.WriteLine($"Filter: {Join(Filter({"ada", "alan", "grace"}, "al"), ",")}")
End Sub
End Module
-- Mid is 1-based, Substring is 0-based
Mid(text, 6, 5) = ' Worl'
text.Substring(6, 5) = 'World'
Mid(text, 7, 5) = 'World'
Mid(text, 7) = 'World!'
Left(text, 5) = 'Hello'
Right(text, 6) = 'World!'
Left(text, 99) = 'Hello World!'
Mid(text, 99) = ''
text.Substring(99) throws ArgumentOutOfRangeException
Mid(text, 0, 2) throws ArgumentException
-- Searching: 0 means not found for InStr, -1 for IndexOf
InStr(text, "o") = 5
InStr(6, text, "o") = 8
InStrRev(text, "o") = 8
InStr(text, "z") = 0
text.IndexOf("o") = 4
text.IndexOf("z") = -1
text.LastIndexOf("o") = 7
text.Contains("World") = True
-- Trim: the VB function removes only spaces
Trim(padded).Length = 11
padded.Trim().Length = 5
LTrim / RTrim lengths = 11 / 11
-- Case, length, repeat
UCase / LCase = HELLO WORLD! / hello world!
Len(text) = 12
Space(3) & StrDup = ' ' 'aaa'
ToTitleCase = The Lord Of The Rings
-- Replace, Split, Join
Replace(text, "l", "L") = HeLLo WorLd!
Replace(text, "l", "L", 5) = o WorLd!
Replace(text, "l", "L", 1, 2) = HeLLo World!
text.Replace("l", "L") = HeLLo WorLd!
Split keeps empty entries: 4 -> a|b||c
String.Split with options: 3 -> a|b|c
Split("a b c") default delimiter is a space: 3
Split with limit: a|b,c,d
Filter: alan
Source: Strings Class
| VB function | Counterpart on String |
What differs |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
1-based and |
|
|
The VB functions remove only the space character U+0020: tabs and line breaks stay. |
|
|
The same current-culture conversion; use |
|
|
|
|
|
The delimiter defaults to a single space; empty entries are kept; |
|
|
Argument order differs. |
|
|
Returns -1, 0 or 1, and the compare mode comes from |
|
|
|
|
|
|
|
|
They also truncate to |
|
|
Returns the elements that contain the text, as an array, with a |
|
|
VB6-style named and custom formats, see the dates page. |
|
|
Windows only: the analyzer reports CA1416, "This call site is reachable on all platforms … only supported on: `windows`", and so the example avoids it. The narrow/wide, katakana/hiragana and Chinese conversions have no .NET equivalent outside Windows |
|
|
|
Which one should you use? There is no single rule, but these defaults are close to universal:
-
For new code prefer the
Stringmembers (Substring,IndexOf,Contains,StartsWith,Trim,Replace,Split,PadLeft,String.Join): they are the same members C# developers use, they takeStringComparison, and they do not depend onOption Compare. -
The
Stringsfunctions that have no good counterpart are fine:MidandLeft/Rightwhen you want "cut politely, never throw",StrDup,Space,InStrRevand theLikeoperator. -
When you call
Left,Right,MidorLeninside a class that has a member with the same name (a WinFormsFormhas aLeftproperty, and aControlhasRight), the member wins: qualify the call asStrings.Left(...). -
A
Stringsfunction acceptsNothingand returns a sensible value; theStringmember needs a non-Nothingreceiver.
The Mid statement
Mid is also a statement: Mid(target, start, length) = replacement overwrites characters of the string variable target in place, from the 1-based position start. The string is still immutable, so the statement assigns a new string to the variable;
it never makes the string longer or shorter, uses at most length characters (and at most the characters left in target), and with the replacement shorter than the field, keeps the remaining characters. The first
argument must be a writable variable or property, not an expression or a literal.
Option Strict On
Module Ex04
Sub Main()
' The Mid statement overwrites characters in place; it never changes the length.
Dim label = "Title: Dune "
Mid(label, 8) = "Emma"
Console.WriteLine($"[{label}]")
Dim code = "ABCDEFGH"
Mid(code, 3, 2) = "xyz"
Console.WriteLine($"Mid(code, 3, 2) = ""xyz"" -> {code}")
Mid(code, 7) = "123456"
Console.WriteLine($"Mid(code, 7) = ""123456"" -> {code}")
' The LSet and RSet functions pad or truncate to a fixed width.
Console.WriteLine("[" & LSet("left", 10) & "] [" & RSet("right", 10) & "] [" & LSet("truncated", 4) & "]")
' Strings are immutable, so the usual replacement is to build a new string.
Dim original = "ISBN 978-0-441-17271-9"
Dim masked = original.Substring(0, 9) & New String("*"c, original.Length - 9)
Console.WriteLine(masked)
Dim chars = original.ToCharArray()
chars(0) = "i"c
Console.WriteLine(New String(chars))
Console.WriteLine(original.Insert(4, ":").Remove(0, 1).PadLeft(25, "_"c))
End Sub
End Module
[Title: Emma ]
Mid(code, 3, 2) = "xyz" -> ABxyEFGH
Mid(code, 7) = "123456" -> ABxyEF12
[left ] [ right] [trun]
ISBN 978-*************
iSBN 978-0-441-17271-9
___SBN: 978-0-441-17271-9
Source: Mid Statement (Visual Basic)
Comparing strings
The question "are these two strings equal?" has at least four answers, depending on what "equal" means for your data. Visual Basic hides some of them behind the = operator, and the table shows what each way of asking does.
| How you ask | Rule | Notes |
|---|---|---|
|
|
Binary compares UTF-16 code units; Text compares case-insensitively using the current culture’s |
|
Ordinal |
Never reads |
|
Ordinal, ignoring simple case differences |
The right test for identifiers, file extensions and protocol keywords |
|
Current culture |
Sorting for people: "a" sorts before "B" even though 97 > 66 |
|
UTF-16 code unit order |
Fast and culture-independent; the order is not alphabetical |
|
|
Returns -1, 0 or 1 |
|
|
Wildcards, not a regular expression (see below) |
|
Current culture for |
A common surprise; always pass a |
|
Ordinal |
There are overloads that take |
Option Compare
Option Compare Binary (the default of the SDK, and of VB itself) and Option Compare Text are file-level statements that appear before any declaration, like Option Strict. They determine the comparison used by the string operators, Select Case, and, as the default for their
Compare argument, by InStr, StrComp, Replace, Split and Filter. They have no effect on String members, Dictionary keys, List.Contains, Sort or LINQ: those use the comparer you pass or the type’s default. The project property <OptionCompare>Text</OptionCompare>
changes the default for the whole project; do not do that, because it silently changes what = means in code that was written assuming Binary. The page
Option Statements covers the statements and the project properties in full.
Option Strict On
Option Compare Binary
Module Ex05
Sub Main()
Dim a = "apple"
Dim b = "Banana"
Dim c = "APPLE"
Console.WriteLine("-- Operators follow Option Compare (Binary: UTF-16 code unit order)")
Console.WriteLine($"a = c : {a = c}")
Console.WriteLine($"a < b : {a < b} (97 is greater than 66)")
Console.WriteLine($"a Like ""APP*"" : {a Like "APP*"}")
Console.WriteLine("-- StrComp, InStr and Replace take an explicit CompareMethod")
Console.WriteLine($"StrComp(a, c) = {StrComp(a, c)}")
Console.WriteLine($"StrComp(a, c, CompareMethod.Text) = {StrComp(a, c, CompareMethod.Text)}")
Console.WriteLine($"InStr(""Shelf"", ""HE"") = {InStr("Shelf", "HE")}")
Console.WriteLine($"InStr(1, ""Shelf"", ""HE"", CompareMethod.Text) = {InStr(1, "Shelf", "HE", CompareMethod.Text)}")
Console.WriteLine($"Replace(""Shelf"", ""SH"", ""-"", Compare:=CompareMethod.Text) = {Replace("Shelf", "SH", "-", Compare:=CompareMethod.Text)}")
Console.WriteLine("-- The .NET methods never read Option Compare")
Console.WriteLine($"a.Equals(c) = {a.Equals(c)}")
Console.WriteLine($"String.Equals(a, c, OrdinalIgnoreCase) = {String.Equals(a, c, StringComparison.OrdinalIgnoreCase)}")
Console.WriteLine($"String.Compare(a, b) = {String.Compare(a, b)} (current culture, here invariant)")
Console.WriteLine($"String.CompareOrdinal(a, b) = {String.CompareOrdinal(a, b)}")
Console.WriteLine($"a.StartsWith(""AP"", StringComparison.OrdinalIgnoreCase) = {a.StartsWith("AP", StringComparison.OrdinalIgnoreCase)}")
Console.WriteLine($"a.Contains(""PPL"", StringComparison.OrdinalIgnoreCase) = {a.Contains("PPL", StringComparison.OrdinalIgnoreCase)}")
Console.WriteLine("-- Sorting with comparers")
Dim titles As String() = {"banana", "Cherry", "apple", "Apple", "cherry"}
Dim ordinal = titles.OrderBy(Function(t) t, StringComparer.Ordinal).ToArray()
Dim ignoreCase = titles.OrderBy(Function(t) t, StringComparer.OrdinalIgnoreCase).ThenBy(Function(t) t, StringComparer.Ordinal).ToArray()
Dim cultural = titles.OrderBy(Function(t) t, StringComparer.InvariantCulture).ToArray()
Console.WriteLine("Ordinal: " & String.Join(" ", ordinal))
Console.WriteLine("OrdinalIgnoreCase: " & String.Join(" ", ignoreCase))
Console.WriteLine("InvariantCulture: " & String.Join(" ", cultural))
End Sub
End Module
-- Operators follow Option Compare (Binary: UTF-16 code unit order)
a = c : False
a < b : False (97 is greater than 66)
a Like "APP*" : False
-- StrComp, InStr and Replace take an explicit CompareMethod
StrComp(a, c) = 1
StrComp(a, c, CompareMethod.Text) = 0
InStr("Shelf", "HE") = 0
InStr(1, "Shelf", "HE", CompareMethod.Text) = 2
Replace("Shelf", "SH", "-", Compare:=CompareMethod.Text) = -elf
-- The .NET methods never read Option Compare
a.Equals(c) = False
String.Equals(a, c, OrdinalIgnoreCase) = True
String.Compare(a, b) = -1 (current culture, here invariant)
String.CompareOrdinal(a, b) = 31
a.StartsWith("AP", StringComparison.OrdinalIgnoreCase) = True
a.Contains("PPL", StringComparison.OrdinalIgnoreCase) = True
-- Sorting with comparers
Ordinal: Apple Cherry apple banana cherry
OrdinalIgnoreCase: Apple apple banana Cherry cherry
InvariantCulture: apple Apple banana cherry Cherry
Source: Option Compare Statement
The same questions in a file that says Option Compare Text:
Option Strict On
Option Compare Text
Module Ex06
Sub Main()
Dim a = "apple"
Dim c = "APPLE"
Console.WriteLine($"a = c : {a = c}")
Console.WriteLine($"a < ""Banana"" : {a < "Banana"}")
Console.WriteLine($"a Like ""APP*"" : {a Like "APP*"}")
Console.WriteLine($"InStr(""Shelf"", ""HE"") = {InStr("Shelf", "HE")}")
Console.WriteLine($"StrComp(a, c) = {StrComp(a, c)}")
Select Case "YES"
Case "yes", "y"
Console.WriteLine("Select Case matched without regard to case")
Case Else
Console.WriteLine("no match")
End Select
' The .NET members still ignore Option Compare.
Console.WriteLine($"a.Equals(c) : {a.Equals(c)}")
Console.WriteLine($"a.Contains(""PPL"") : {a.Contains("PPL")}")
' Dictionary keys use the comparer given to the constructor, never Option Compare.
Dim byName As New Dictionary(Of String, Integer)()
byName("Ada") = 1
Console.WriteLine($"Dictionary lookup of ""ADA"": {byName.ContainsKey("ADA")}")
Dim relaxed As New Dictionary(Of String, Integer)(StringComparer.OrdinalIgnoreCase)
relaxed("Ada") = 1
Console.WriteLine($"OrdinalIgnoreCase lookup of ""ADA"": {relaxed.ContainsKey("ADA")}")
End Sub
End Module
a = c : True
a < "Banana" : True
a Like "APP*" : True
InStr("Shelf", "HE") = 2
StrComp(a, c) = 0
Select Case matched without regard to case
a.Equals(c) : False
a.Contains("PPL") : False
Dictionary lookup of "ADA": False
OrdinalIgnoreCase lookup of "ADA": True
Source: Like Operator (Visual Basic)
Notice what the second program shows: a = c and the Like operator turn case-insensitive, a.Equals(c) does not, and a Dictionary(Of String, Integer) created with the default comparer does not either. Using Option Compare Text
to get case-insensitive keys is therefore a mistake that works in one place and fails in the next; pass StringComparer.OrdinalIgnoreCase to the collection.
Culture, ordinal and invariant
(CultureInfo.CurrentCulture unless one is passed): correct for text shown to people, but the result can differ between users and between operating systems, because since .NET 5 the rules come from ICU on Linux, macOS
and recent Windows, and from NLS on older Windows. Invariant culture applies the same culture-sensitive rules for an English-like "no particular culture", intended for persisted data. The guidance of the .NET best-practices for strings applies unchanged to VB:
-
Use ordinal (
StringComparison.Ordinal,OrdinalIgnoreCase) for identifiers, keys, file names, paths, URLs, protocol tokens and any other data that a program, not a person, reads. -
Use culture-sensitive comparison for text that you sort or search for people, passing the culture explicitly (
String.Compare(a, b, culture, CompareOptions.IgnoreCase)). -
Convert case with
ToUpperInvariantorToLowerInvariantbefore comparing machine data, never withToLower()(the TurkishI, below), and preferOrdinalIgnoreCaseto converting at all.
Option Strict On
Imports System.Globalization
Module Ex07
Sub Main()
Dim turkish = CultureInfo.GetCultureInfo("tr-TR")
Dim english = CultureInfo.GetCultureInfo("en-US")
Dim word = "TITLE"
Dim file = "i"
Console.WriteLine($"ToLower(en-US) : {word.ToLower(english)}")
Console.WriteLine($"ToLower(tr-TR) : {word.ToLower(turkish)}")
Console.WriteLine($"ToLowerInvariant : {word.ToLowerInvariant()}")
Console.WriteLine($"ToUpper(tr-TR) of i: {file.ToUpper(turkish)} (U+{AscW(file.ToUpper(turkish)(0)):X4})")
Console.WriteLine($"ToUpperInvariant : {file.ToUpperInvariant()}")
' A classic bug: a protocol keyword compared after a culture-sensitive case change.
Dim command = "TITLE"
Console.WriteLine($"command.ToLower(tr-TR) = ""title"": {command.ToLower(turkish) = "title"}")
Console.WriteLine($"String.Equals ordinal ignore case: {String.Equals(command, "title", StringComparison.OrdinalIgnoreCase)}")
' Culture-aware searching and comparison
Dim cafe = "caf" & ChrW(&HE9)
Dim cafeDecomposed = "cafe" & ChrW(&H301)
Console.WriteLine($"Ordinal equal: {String.Equals(cafe, cafeDecomposed, StringComparison.Ordinal)}")
Console.WriteLine($"InvariantCulture equal: {String.Equals(cafe, cafeDecomposed, StringComparison.InvariantCulture)}")
Console.WriteLine($"IndexOf(""e"") ordinal: {cafeDecomposed.IndexOf("e", StringComparison.Ordinal)}, culture: {cafeDecomposed.IndexOf("e", StringComparison.InvariantCulture)}")
Dim spanish = CultureInfo.GetCultureInfo("es-ES")
Dim swedish = CultureInfo.GetCultureInfo("sv-SE")
Console.WriteLine($"Compare(""z"", ""a-umlaut"", sv-SE) = {String.Compare("z", "ä", swedish, CompareOptions.None)}")
Console.WriteLine($"Compare(""z"", ""a-umlaut"", es-ES) = {String.Compare("z", "ä", spanish, CompareOptions.None)}")
Dim options = CompareOptions.IgnoreCase Or CompareOptions.IgnoreNonSpace
Console.WriteLine($"Ignoring case and accents: {String.Compare(cafe.ToUpperInvariant(), "cafe", english, options)}")
Console.WriteLine($"Current culture while this ran: '{CultureInfo.CurrentCulture.Name}' ({CultureInfo.CurrentCulture.EnglishName})")
End Sub
End Module
ToLower(en-US) : title
ToLower(tr-TR) : tıtle
ToLowerInvariant : title
ToUpper(tr-TR) of i: İ (U+0130)
ToUpperInvariant : I
command.ToLower(tr-TR) = "title": False
String.Equals ordinal ignore case: True
Ordinal equal: False
InvariantCulture equal: True
IndexOf("e") ordinal: 3, culture: -1
Compare("z", "a-umlaut", sv-SE) = -1
Compare("z", "a-umlaut", es-ES) = 1
Ignoring case and accents: 0
Current culture while this ran: '' (Invariant Language (Invariant Country))
The Turkish line is the classic failure: with the culture tr-TR the lower-case form of I is the dotless ı (U+0131), so "TITLE".ToLower() is not "title" and a keyword comparison written that way fails on a Turkish machine. In the same way,
the Swedish alphabet sorts ä after z and the Spanish one does not, and the decomposed form of café is culture-equal but ordinal-different from the precomposed one. A program that only
ever runs with a fixed English culture does not see any of this, which is why the problem stays hidden until a customer reports it. The project property <InvariantGlobalization>true</InvariantGlobalization> removes ICU entirely and makes every culture behave like the invariant culture, which is the usual setting for container images but disables the culture-specific results above.
Building strings
Concatenating a few values with &, String.Concat or an interpolated string is fine and the compiler turns several operands into a single String.Concat call. The pattern to avoid is a loop that appends to a string with
&=: each step creates a new string and copies the old one, so n appends cost O(n2) time and produce n short-lived garbage strings. Use System.Text.StringBuilder, or String.Join when you already have a sequence, or a TextWriter when the destination is a stream.
StringBuilder is mutable, grows by chunks, has chainable Append, AppendLine, AppendFormat, AppendJoin, Insert, Remove and Replace, and is not thread-safe.
Option Strict On
Imports System.Text
Module Ex08
Sub Main()
Dim titles As String() = {"Dune", "Emma", "Ulysses"}
Dim csv = String.Join(",", titles)
Console.WriteLine(csv)
Console.WriteLine(String.Concat("a", "b", "c") & String.Concat(titles))
Console.WriteLine(String.Join(", ", titles.Select(Function(t) t.Length)))
' Repeated & in a loop copies the whole string each time: O(n squared).
Dim slow = ""
For i = 1 To 5
slow &= i & ";"
Next
Console.WriteLine(slow)
Dim builder As New StringBuilder()
For Each t In titles
If builder.Length > 0 Then builder.Append(" | ")
builder.Append(t).Append(" (").Append(t.Length).Append(")"c)
Next
builder.AppendLine().AppendFormat("{0} titles", titles.Length)
builder.Insert(0, ">> ")
Console.WriteLine(builder.ToString())
Console.WriteLine($"Length = {builder.Length}, capacity >= length: {builder.Capacity >= builder.Length}")
builder.Clear()
Console.WriteLine($"After Clear: Length = {builder.Length}")
' Spans can be used inside one expression, but a local variable cannot have a span type in VB.
Dim isbn = "978-0-441-17271-9"
Console.WriteLine($"Span slice: {isbn.AsSpan(4, 1).ToString()}, length {isbn.AsSpan(4, 1).Length}")
Console.WriteLine($"Span search: {isbn.AsSpan().IndexOf("-"c)}")
Dim digits = isbn.Where(AddressOf Char.IsDigit).Count()
Console.WriteLine($"Digits via LINQ: {digits}")
Console.WriteLine($"String.Format: {String.Format("{0,-6}|{1,6:N1}|{2:P0}", "Dune", 12.34, 0.256)}")
Dim invariantText = FormattableString.Invariant($"{1234.5:N2} on {#2026-10-19#:yyyy-MM-dd}")
Console.WriteLine(invariantText)
Dim f As FormattableString = $"{titles(0)} and {titles(1)}"
Console.WriteLine($"Format = '{f.Format}', ArgumentCount = {f.ArgumentCount}")
End Sub
End Module
Dune,Emma,Ulysses
abcDuneEmmaUlysses
4, 4, 7
1;2;3;4;5;
>> Dune (4) | Emma (4) | Ulysses (7)
3 titles
Length = 45, capacity >= length: True
After Clear: Length = 0
Span slice: 0, length 1
Span search: 3
Digits via LINQ: 13
String.Format: Dune | 12.3|26 %
1,234.50 on 2026-10-19
Format = '{0} and {1}', ArgumentCount = 2
Source: StringBuilder Class
Spans. ReadOnlySpan(Of Char) and Span(Of Char) are ref structs. Visual Basic 17.13 can pass them to methods and call their members in an expression (isbn.AsSpan(4, 1).ToString()), but it cannot declare a local variable,
field or parameter of a span type: the line Dim part As ReadOnlySpan(Of Char) = ... fails with BC30668 ("`ReadOnlySpan(Of Char)` is obsolete: Types with embedded references are not supported in this version of your compiler"). VB also has no stackalloc. For
allocation-free text work, write the hot path in a C# library or use the String and StringBuilder APIs, and see the memory and spans notes in
Unsafe Code, Spans and Performance.
Pattern matching: Like and regular expressions
The Like operator compares a string with a pattern that has five metacharacters: ? (one character), * (any number of characters, also none), # (one digit), [list] (one character from the list or a range such as [A-C]), and [!list] (one character not in the list).
Put a metacharacter in brackets to match it literally: a[*]b. The case sensitivity follows Option Compare. Like works on .NET: it is implemented by LikeOperator in Microsoft.VisualBasic.Core, and the
following program, built and run on .NET 10, shows that it does. The Language Reference page for the operator still carries a note that it is not supported on .NET Core, which is out of date (checked 2026-10-19).
Option Strict On
Module Ex12
Sub Check(text As String, pattern As String)
Console.WriteLine($"{text,-12} Like {pattern,-12} -> {text Like pattern}")
End Sub
Sub Main()
Check("Dune", "D*")
Check("Dune", "D??e")
Check("Dune", "d*")
Check("2026", "####")
Check("A12", "[A-C]##")
Check("D12", "[!A-C]##")
Check("a*b", "a[*]b")
Check("978-0-441", "978-#-###")
Check("ISBN", "")
Dim padded = "Dune".PadRight(8, "."c) & "1965".PadLeft(6)
Console.WriteLine(padded)
Dim catalog As (Title As String, Year As Integer)() = {("Dune", 1965), ("Emma", 1815), ("Ulysses", 1922)}
For Each row In catalog
Console.WriteLine($"{row.Title,-10}{row.Year,6}")
Next
Console.WriteLine(New String("="c, 16))
End Sub
End Module
Dune Like D* -> True
Dune Like D??e -> True
Dune Like d* -> False
2026 Like #### -> True
A12 Like [A-C]## -> True
D12 Like [!A-C]## -> True
a*b Like a[*]b -> True
978-0-441 Like 978-#-### -> True
ISBN Like -> False
Dune.... 1965
Dune 1965
Emma 1815
Ulysses 1922
================
Source: Like Operator (Visual Basic)
For anything beyond that, use System.Text.RegularExpressions.Regex. VB string literals need no escaping of backslashes, which makes patterns easier to read than in C# without the verbatim prefix. Points specific to VB and to current .NET:
-
Named groups are read with
m.Groups("year"); test.Successon a group for an optional part. -
Always give a match timeout on patterns that process untrusted input (
New Regex(pattern, options, TimeSpan.FromSeconds(1))): nested quantifiers such as(a+)+$have exponential backtracking and raiseRegexMatchTimeoutExceptionwhen the limit is reached.RegexOptions.NonBacktracking(.NET 7 and later) avoids backtracking altogether, with a smaller feature set. -
RegexOptions.CultureInvarianttogether withIgnoreCaseavoids the TurkishIproblem;RegexOptions.Compiledtrades start-up time for speed. -
Escape user input before embedding it in a pattern with
Regex.Escape. -
The
GeneratedRegexsource generator of .NET 7 is not available to VB: its attribute goes on apartialmethod with a return value, and VB’s partial methods must beSub(BC31437, "partial methods must be Subs"). AShared ReadOnlyfield holding aNew Regex(...)is the VB replacement. -
The static methods (
Regex.IsMatch(text, pattern)) cache recently used patterns, which is fine for a few patterns and a poor choice in a hot loop with many.
Option Strict On
Imports System.Text.RegularExpressions
Module Ex09
Sub Main()
Dim lines As String() = {
"Dune (1965) by Frank Herbert, ISBN 978-0-441-17271-9",
"Emma (1815) by Jane Austen",
"no year here"
}
' Verbatim by nature: backslashes in VB string literals are ordinary characters.
Dim pattern As New Regex("^(?<title>.+?) \((?<year>\d{4})\)(?: by (?<author>[^,]+))?", RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1))
For Each line In lines
Dim m = pattern.Match(line)
If m.Success Then
Dim author = m.Groups("author").Value
Console.WriteLine($"{m.Groups("title").Value} | {CInt(m.Groups("year").Value) + 1} | {If(m.Groups("author").Success, author, "(none)")}")
Else
Console.WriteLine($"No match: {line}")
End If
Next
Dim isbn = New Regex("\b97[89](?:-?\d){10}\b")
Console.WriteLine($"ISBN found: {isbn.Match(lines(0)).Value}")
Dim spaced = Regex.Replace("a b c", "\s+", " ")
Console.WriteLine(spaced)
Dim shouted = Regex.Replace("shelf catalog", "\b[a-z]", Function(m As Match) m.Value.ToUpperInvariant())
Console.WriteLine(shouted)
Console.WriteLine(String.Join("|", Regex.Split("one, two;three", "[,;]\s*")))
Console.WriteLine($"IsMatch ignoring case: {Regex.IsMatch("ISBN", "isbn", RegexOptions.IgnoreCase)}")
Console.WriteLine($"Escaped user input: {Regex.Escape("1+1=2 (really?)")}")
Console.WriteLine($"Count of digits: {Regex.Matches("a1b22c333", "\d+").Count}")
Try
Dim slow As New Regex("^(a+)+$", RegexOptions.None, TimeSpan.FromMilliseconds(50))
slow.IsMatch(New String("a"c, 40) & "!")
Catch ex As RegexMatchTimeoutException
Console.WriteLine("RegexMatchTimeoutException after " & ex.MatchTimeout.TotalMilliseconds & " ms")
End Try
End Sub
End Module
Dune | 1966 | Frank Herbert
Emma | 1816 | Jane Austen
No match: no year here
ISBN found: 978-0-441-17271-9
a b c
Shelf Catalog
one|two|three
IsMatch ignoring case: True
Escaped user input: 1\+1=2\ \(really\?\)
Count of digits: 3
RegexMatchTimeoutException after 50 ms
Source: .NET regular expressions
Encodings and bytes
A String has no encoding; a byte sequence does. System.Text.Encoding converts between them. .NET defaults to UTF-8 for files and streams (File.ReadAllText, StreamReader, Encoding.UTF8), which is not the same as the console or the Windows ANSI code page. The program shows the byte
counts for Café €, what happens with characters that the target cannot represent (? by default, or an exception if you ask for one), the byte order mark, the replacement character for invalid input, and the code pages that .NET Core does not ship.
Option Strict On
Imports System.Text
Module Ex10
Sub Main()
Dim text = "Caf" & ChrW(&HE9) & " " & ChrW(&H20AC)
Console.WriteLine($"UTF-16 code units: {text.Length}")
For Each name In {"utf-8", "utf-16", "utf-16BE", "utf-32", "us-ascii", "iso-8859-1"}
Dim enc = Encoding.GetEncoding(name)
Dim bytes = enc.GetBytes(text)
Console.WriteLine($"{enc.WebName,-12} {bytes.Length,2} bytes: {Convert.ToHexString(bytes)}")
Next
' Bytes that are not valid in the encoding are replaced, silently by default.
Dim latin = Encoding.Latin1.GetBytes(text)
Console.WriteLine($"Latin-1 round trip: {Encoding.Latin1.GetString(latin)}")
Dim strict = Encoding.GetEncoding("us-ascii", New EncoderExceptionFallback(), New DecoderExceptionFallback())
Try
strict.GetBytes(text)
Catch ex As EncoderFallbackException
Console.WriteLine("EncoderFallbackException for U+" & AscW(ex.CharUnknown).ToString("X4"))
End Try
Dim utf8Bom = New UTF8Encoding(encoderShouldEmitUTF8Identifier:=True).GetPreamble()
Console.WriteLine($"UTF-8 BOM: {Convert.ToHexString(utf8Bom)}; Encoding.UTF8 preamble: {Convert.ToHexString(Encoding.UTF8.GetPreamble())}")
Dim broken As Byte() = {&H43, &HFF, &H44}
Console.WriteLine($"Invalid UTF-8 decodes with a replacement character: {Encoding.UTF8.GetString(broken)} (U+{AscW(Encoding.UTF8.GetString(broken)(1)):X4})")
Try
Encoding.GetEncoding(1252)
Catch ex As NotSupportedException
Console.WriteLine("Code page 1252 needs the CodePages provider: " & ex.GetType().Name)
End Try
Try
Console.WriteLine("Chr(200) = " & Chr(200))
Catch ex As NotSupportedException
Console.WriteLine("Chr(200) uses the ANSI code page too: " & ex.GetType().Name)
End Try
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)
Console.WriteLine($"After RegisterProvider: {Encoding.GetEncoding(1252).WebName}, euro = {Convert.ToHexString(Encoding.GetEncoding(1252).GetBytes(ChrW(&H20AC)))}")
Console.WriteLine($"Asc(""A"") = {Asc("A")}, Chr(65) = {Chr(65)}, AscW(""€"") = {AscW("€")}")
Console.WriteLine($"After the provider is registered Chr(200) = {Chr(200)}; ChrW(200) = {ChrW(200)} regardless")
Console.WriteLine($"Base64: {Convert.ToBase64String(Encoding.UTF8.GetBytes("Shelf"))}")
End Sub
End Module
UTF-16 code units: 6
utf-8 9 bytes: 436166C3A920E282AC
utf-16 12 bytes: 430061006600E9002000AC20
utf-16BE 12 bytes: 00430061006600E9002020AC
utf-32 24 bytes: 430000006100000066000000E900000020000000AC200000
us-ascii 6 bytes: 4361663F203F
iso-8859-1 6 bytes: 436166E9203F
Latin-1 round trip: Café ?
EncoderFallbackException for U+00E9
UTF-8 BOM: EFBBBF; Encoding.UTF8 preamble: EFBBBF
Invalid UTF-8 decodes with a replacement character: C�D (U+FFFD)
Code page 1252 needs the CodePages provider: NotSupportedException
Chr(200) uses the ANSI code page too: NotSupportedException
After RegisterProvider: windows-1252, euro = 80
Asc("A") = 65, Chr(65) = A, AscW("€") = 8364
After the provider is registered Chr(200) = È; ChrW(200) = È regardless
Base64: U2hlbGY=
Source: Encoding Class
Rules of thumb: write UTF-8 (without a BOM unless a consumer needs it); use Encoding.Latin1 for ISO-8859-1; register CodePagesEncodingProvider.Instance once at start-up when you must read Windows-1252 or other legacy code pages; never rely on Chr and Asc for non-ASCII text,
use ChrW and AscW; and for a console program that prints non-ASCII text set Console.OutputEncoding = Encoding.UTF8 yourself. The same encoding rules apply to File.ReadAllText/WriteAllText (see
Files, Directories and Streams).
Concatenation: & versus
& is the concatenation operator: it always converts both operands to String and joins them, so "Copies: " & 2 is "Copies: 2" and 1 & 2 is "12". + is the addition operator that, when both operands are strings, also concatenates for compatibility with early BASIC. The two are not interchangeable:
-
With one string and one number,
+tries to convert the string to a number:"1" + 2and"x" + 2are BC30512 underOption Strict On("disallows implicit conversions fromStringtoDouble`"); with strict typing switched off (checked in a scratch project) the first quietly gives the `Double3and the second throwsInvalidCastException("Conversion from string "x" to type 'Double' is not valid") at run time. -
The precedence is different: arithmetic binds tighter than
&, so"Total: " & 1 + 2is "Total: 3" while the addition-only reading would first apply+to a string and a number. -
Nothingis an empty string for&, andNothing + "a"also works but"a" + 1does not. -
Two strings that happen to contain digits (
left + rightin the program) are concatenated by+, which hides a missing conversion: someone later changes one variable toIntegerand the meaning of the line changes.
Always write & for concatenation (and &= for appending), interpolation for formatting, and keep + for numbers.
Option Strict On
Module Ex11
Sub Main()
Dim count As Integer = 2
Dim title As String = "Dune"
' & always concatenates and converts both sides to String.
Console.WriteLine("Copies: " & count)
Console.WriteLine(1 & 2)
Console.WriteLine("Total: " & 1 + 2)
Console.WriteLine("Total: " & (1 + 2))
Console.WriteLine(title & Nothing & "!")
' Concatenation has lower precedence than arithmetic and higher than comparison.
Console.WriteLine("a" & "b" = "ab")
' + on two strings concatenates as well, but it is a numeric operator that falls back to concatenation.
Dim left As String = "10"
Dim right As String = "5"
Console.WriteLine(left + right)
Console.WriteLine(left & right)
Console.WriteLine(CInt(left) + CInt(right))
' Interpolation and String.Format never ask whether + was meant.
Console.WriteLine($"{left}{right} / {count + 1}")
End Sub
End Module
Copies: 2
12
Total: 3
Total: 3
Dune!
True
105
105
15
105 / 3
Comparison with C#
| Topic | Visual Basic | C# |
|---|---|---|
Indexing |
|
|
Escape sequences |
none; |
|
Interpolation |
|
|
UTF-8 literal |
none |
|
|
|
|
Concatenation |
|
|
Case conversion |
|
|
Spans in locals |
not supported (BC30668) |
|
Regex source generator |
not available (BC31437) |
|
String patterns |
|
|
Smart quotes |
accepted as string delimiters |
rejected |
Where the books are outdated. The two books that this section was planned from draw on VB6 and early VB.NET habits in text handling: they use ` for concatenation, they present `Mid("Hello World!", 6, 5)` as "World" when it is `" Worl"` (`Mid` is 1-based, so the word starts at position 7),
and they treat `FormatDateTime` and `Format` as the usual way to format, where interpolation and `ToString(format)` are the current choice. The corrections are all in this page: `&` and interpolation instead of `, the 1-based
Mid, Left, Right and InStr against the 0-based Substring and IndexOf, and the culture rules of ToLower and String.Compare. Their 8-bit view of text (Chr, Asc, ANSI) is
also outdated: .NET text is UTF-16 and files default to UTF-8.
Pitfalls checklist
-
The positions in
Mid,InStrandLeft, and thestartargument ofReplace, are 1-based;SubstringandIndexOfare 0-based;Replacewith a start position drops the beginning. -
Trim(the function) removes only spaces;s.Trim()removes all white space. -
StrDup(n, "ab")repeats the first character only. -
Nothing = ""isTrue, whileNothing Is ""isFalse(the second is an empty string, an object); useString.IsNullOrEmptyfor "missing or empty". -
Option Compare Textaffects operators,Select Case,Like,InStrandStrComponly; dictionaries,List.ContainsandString.Equalsignore it. -
ToLower()/ToUpper()use the current culture: use theInvariantvariants orOrdinalIgnoreCasefor machine data. -
StartsWith(String),IndexOf(String)andCompareare culture-sensitive by default;Contains,ReplaceandSplitare ordinal. -
A multiline string literal contains the line endings of the source file.
-
Curly quotes pasted into code are valid string delimiters.
-
The Asian conversions of
StrConvare Windows-only;ChrandAscabove 127 depend on a code page. -
s &= xin a loop is quadratic; useStringBuilderorString.Join. -
+between a number and a string is a conversion error underOption Strict On;&never is. -
Lengthcounts UTF-16 code units, not characters;MidandSubstringcan cut surrogate pairs. -
No VB local can have a span type, and
GeneratedRegexcannot be used.