VB.NET Type conversions (Part 1)
Introduction
Developers work with various types of data ranging from displaying information collected from user inputting which is then stored in a container, reading information from various sources such as web services, native files to databases. One thing is common, at one point the data is represented as an object and usually a string. In this article developers will learn how to work with raw data to convert to a type suitable for tasks in Visual Studio solutions using defensive programming which means in most task data read or written may not be in the expected format to be stored in designated containers like databases, json, xml or another format.
Information and code samples are geared towards both novice and intermediate skill level which are demonstrated in unit test, Windows Forms and WPF projects. In some cases, there will be class projects which can be used in a Visual Studio solution by copying one of the class projects into an existing Visual Studio solution followed by (if needed) changing the .NET Framework version in the class project to match the .NET Framework used in an existing Visual Studio solution. The base .NET Framework used in code in this article is 4.7.2. If a Framework below 3.5 is used code presented may not work, for example String. IsNullOrWhiteSpace was first introduced in Framework 3.5.
Language extension methods are utilizing in many operations which in some cases utilize LINQ or Lambda, when reviewing these methods do not to hung up on the complexity, instead if the method(s) work simply use them.
The code presented should not be considered that they belong to one platform when presented in ASP.NET, WPF or Windows Forms. For example, exploring code in a WPF project may reveal a useful regular expression pattern or a unit test may lead to a method useful in a Windows Form project
Conventions
All code presented uses the following directives, Option Strict On, Option Infer On. Using these directives assist with having quality code.
A common practice with novice developers to programming or have coded in perhaps JavaScript are not use to strong typing as shown in the code below.
A common practice with novice developers to programming or have coded in perhaps JavaScript are not use to strong typing as shown in the code below.
Option Strict Off
Option Infer Off
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) _
Handles Button1.Click
Dim someInteger = CInt("Does not represent an Integer")
End Sub
End Class
In the code above someInteger variable is an object, not a String which compile but will throw a runtime exception because there was no type checking.
In the following code sample Option Strict On will not compile as Visual Studio wants a type.
Option Strict On
Option Infer Off
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) _
Handles Button1.Click
Dim someInteger = CInt("Does not represent an Integer")
End Sub
End Class
Adding the type.
Option Strict On
Option Infer Off
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) _
Handles Button1.Click
Dim someInteger As Integer = CInt("Does not represent an Integer")
End Sub
End Class
Visual Studio will allow this code to compile even though the conversion will fail as "Does not represent an Integer" can not be represented as an Integer.
By setting Option Strict On provides a decent base for writing quality code. Later assertion will be taught to protect against runtime exceptions when a value can not be converted from one type to another type without any runtime exceptions.
The following code example shows the basics for testing if a string can be converted to an Integer.
Option Strict On
Option Infer On
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) _
Handles Button1.Click
Dim userInput As String = "Does not represent an Integer"
If Not String.IsNullOrWhiteSpace(userInput) Then
Dim someInteger As Integer = 0
If Integer.TryParse(userInput, someInteger) Then
' converted
Else
' failed to convert
End If
Else
' variable has no content
End If
End Sub
End Class
Naming variables and controls
Common practice for novice developers is to provide meaningless names for variable and controls which does not lend to easily reading code later on which leads to code which is difficult or impossible to maintain. Code presented in this article uses a simple convention, name variables and controls so that a non-developer can read your code and have an idea what variables and controls represent. For example for a input used to obtain a customer’s first name, firstNameTextBox rather than TextBox1.
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) _
Handles Button1.Click
Dim firstName As String = firstNameTextBox.Text
End Sub
End Class
Note in the last code example the button has a generic name, does not describe what the button is for. Naming controls and events are both important so here the button Click event has a meaningful name.
Public Class Form1
Private Sub processCustomerInputButton_Click(sender As Object, e As EventArgs) _
Handles processCustomerInputButton.Click
Dim firstName As String = firstNameTextBox.Text
Dim lastName As String = lastNameTextBox.Text
End Sub
End Class
When naming variables in general use names that make it apparent the data type. Some developers prefer a prefix e.g. for a string strFirstName where when defining a variable today in Visual Studio hovering over a variable will indicate it’s type so rather than strFirstName use firstName which 99 percent of the time is a string. In short lose the three-character prefix, give variables meaning full names.
Naming variables can change dependent on scope e.g. a private variable with a leading underscore quickly indicates you are working with a privately scoped variable e.g. _firstName while firstName would be in scope of the current method. When passing arguments/parameters to a method consider a consistent naming convention.
In the following code sample shows using an underscore for a Private class level variable which is set by a parameter passed in via the new constructor which is available to methods within this class.
Public Class DataOperations
Public Sub New()
End Sub
Private _customerIdentifer As Integer
''' <summary>
''' Setup for working with a specific customer
''' </summary>
''' <param name="pCustomerIdentifier">Existing Customer primary key</param>
Public Sub New(pCustomerIdentifier As Integer)
_customerIdentifer = pCustomerIdentifier
End Sub
End Class
String assertion
Anytime required input is required for instance to create an object always assume one or more inputs do not have values. For instance, adding a new customer which requires first and last name along with an email address using TextBox controls for input and a button to create the new customer.
First attempt which asserts each TextBox, if even one is empty the requirement for all inputs to be entered has not been meant.
Public Class Form1
Private Sub addCustomerButton_Click(sender As Object, e As EventArgs) _
Handles addCustomerButton.Click
If String.IsNullOrWhiteSpace(firstNameTextBox.Text) OrElse
String.IsNullOrWhiteSpace(lastNameTextBox.Text) OrElse
String.IsNullOrWhiteSpace(personalEmailAddressTextBox.Text) Then
MessageBox.Show("All fields are required")
Else
Dim customer As New Customer With
{
.FirstName = firstNameTextBox.Text,
.LastName = lastNameTextBox.Text,
.EmailAddress = personalEmailAddressTextBox.Text
}
End If
End Sub
End Class
Second attempt validates all inputs (TextBox controls) are populated using Enumerable.Any method. Which works well when all inputs are required.
Public Class Form1
Private Sub addCustomerButton_Click(sender As Object, e As EventArgs) _
Handles addCustomerButton.Click
If Controls.OfType(Of TextBox).Any(Function(textBox) String.IsNullOrWhiteSpace(textBox.Text)) Then
MessageBox.Show("All fields are required")
Else
Dim customer As New Customer With
{
.FirstName = firstNameTextBox.Text,
.LastName = lastNameTextBox.Text,
.EmailAddress = personalEmailAddressTextBox.Text
}
End If
End Sub
End Class
Third attempt, using control validation. In the code sample below Control.Validated event is used along with ContainerControl.ValidateChildren Method to cause in this case the firstNameTextBox to perform validation.
Public Class Form1
Private Sub addCustomerButton_Click(sender As Object, e As EventArgs) _
Handles addCustomerButton.Click
If ValidateChildren() Then
MessageBox.Show("Go to go")
End If
End Sub
Private Sub firstNameTextBox_Validating(sender As Object, e As CancelEventArgs) _
Handles firstNameTextBox.Validating
If String.IsNullOrWhiteSpace(firstNameTextBox.Text) Then
ErrorProvider1.SetError(firstNameTextBox, "first name is required")
e.Cancel = True
Else
ErrorProvider1.SetError(firstNameTextBox, "")
e.Cancel = False
End If
End Sub
End Class
String to numerics
Once a required string has been established to have content usually the value will be stored as a string or converted to another type. In the following WPF window there are three TextBox controls.
The first TextBox assertion is done by setting up an event for PreviewTextInput. In the code behind a Regular Expression is used to determine if the text entered can be represented as an Integer.
Private Shared ReadOnly _regex As New Regex("[^0-9.-]+")
Private Shared Function IsTextAllowed(ByVal text As String) As Boolean
Return _regex.IsMatch(text)
End Function
''' <summary>
''' Used to prevent information entered to be non-integer values
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
Private Sub AssertIntegerTextBox_OnPreviewTextInput(
sender As Object,
e As TextCompositionEventArgs)
e.Handled = _regex.IsMatch(e.Text)
End Sub
There is still a need to determine if there has been a value entered which in this case is done using String.IsNullOrWhiteSpace as in examples done above.
Private Sub IntegerOnlyTextBox_OnClick(
sender As Object,
e As RoutedEventArgs)
If String.IsNullOrWhiteSpace(assertIntegerTextBox.Text) Then
MessageBox.Show("No value")
Else
MessageBox.Show($"Value is {assertIntegerTextBox.Text}")
End If
End Sub
In the middle TextBox control a conversion is performed without checking to see if the value can be represented as an Integer. Novice developers tend to do this at least once without a try/catch. Then not knowing any better will not only surround this code with a try/catch they will have an empty catch. About the only time it is okay to have an empty catch is when traversing a folder structure were the operator does not have permissions to one or more folders.
In the bottom TextBox a decimal has been entered were an Integer was expected. An option is to use Decimal.TryParse, use Decimal.Truncate to get the Integer value which may be okay in some task while others this may not be okay dependent on business requirements along with operator thinking what happen to the decimal entered?
Private Sub tryParseAssertionButton_Click(
sender As Object,
e As RoutedEventArgs) Handles tryParseAssertionButton.Click
If Not String.IsNullOrWhiteSpace(tryparseIntegerTextBox.Text) Then
Dim value As Integer = 0
If Integer.TryParse(tryparseIntegerTextBox.Text, value) Then
MessageBox.Show($"You entered a valid integer: {value}")
Else
MessageBox.Show($"'{tryparseIntegerTextBox.Text}' could not be converted to an integer")
End If
Else
MessageBox.Show("Please enter a value")
End If
End Sub
In a Windows Form project one method to assert that a input in a TextBox is an Integer is using KeyPress event of the TextBox.
Private Sub AssertionOnInteger_KeyPressForOnlyIntegers(
sender As Object,
e As KeyPressEventArgs) _
Handles keyPressAssertionOnIntegerTextBox.KeyPress
'97 - 122 = Ascii codes for simple letters
'65 - 90 = Ascii codes for capital letters
'48 - 57 = Ascii codes for numbers
If Asc(e.KeyChar) <> 8 Then
If Asc(e.KeyChar) < 48 Or Asc(e.KeyChar) > 57 Then
e.Handled = True
End If
End If
End Sub
Note there are other ways to perform assertion on string and converting to numbers using custom validator components and by implementing a component with IExtenderProvider Interface.
Generics
The best method to perform conversions is to have same named methods (or extension methods )for each type for maintainability although it's possible to write one method for various types.
The following extension method can be used against Integer, Decimal, Double and Long.
''' <summary>
''' Converts a string to a Nullable type as per T
''' </summary>
''' <typeparam name="T">Type to convert too</typeparam>
''' <param name="sender">String to work from</param>
''' <returns>Nullable of T</returns>
''' <example>
''' <code>
''' Dim value = "12".ToNullable(Of Integer)
''' If value.HasValue Then
''' - use value
''' Else
''' - do not use value
''' </code>
''' </example>
<CompilerServices.Extension>
Public Function ToNullable(Of T As Structure)(sender As String) As T?
Dim result As New T?()
Try
If Not String.IsNullOrWhiteSpace(sender) Then
Dim converter As TypeConverter = TypeDescriptor.GetConverter(GetType(T))
result = CType(converter.ConvertFrom(sender), T)
End If
Catch
' don't care, caller should use HasValue before accessing the value.
End Try
Return result
End Function
Example usage converting from string to integer. If the value of ToNullableFailGracefullyTextBox TextBox text at present is "Karen", HasValue will return False while if the value in the TextBox was 12 HasValue would be True.
Dim nullableIntegerValue As Integer? =
ToNullableFailGracefullyTextBox.Text.ToNullable(Of Integer)
If nullableIntegerValue.HasValue Then
Dim integerValue = nullableIntegerValue.Value
MessageBox.Show($"Converted successfully")
Else
MessageBox.Show($"{ToNullableFailGracefullyTextBox.Text} could not be converted")
End If
To use the extension method to test if the TextBox contains a decimal value.
ToNullableFailGracefullyTextBox.Text = "12.4"
Dim nullableDecValue = ToNullableFailGracefullyTextBox.Text.ToNullable(Of Decimal)
To try out the various generic methods, see the project ParsingAndConvertingWindowsForms.
String array to numeric array
In this section conversion from string array to Integer arrays are discussed, in the repository there are mirror method for Long, Decimal and Double in NumericHelpers project.
Working with arrays can prove challenging when first starting out having to convert a string array which should contain only Integer, Decimal or Double values in each element of an array. A common practice is to use code shown below.
Dim stringArray = {"123", "456", "789"}
Dim intList = stringArray.
ToList().
ConvertAll(Function(stringValue) Integer.Parse(stringValue))
Which will convert each element in the string array by first accessing th e array using .ToList then using ConvertAll to parse using Integer.Parse each element into an Integer.
Since each element can represent Integers the conversion is successful. If an element can not be converted a runtime exception is thrown. In the following code sample one element can not be converted because there is a space in between two numbers in the middle element.
Dim stringArray = {"123", "45 6", "789"}
Dim intList = stringArray.
ToList().
ConvertAll(Function(stringValue) Integer.Parse(stringValue))
Assertion
Anytime there is a possible chance something is not as expected, in this case if one or more elements can not be converted this is were assertion comes in.
In the following example rather than using Integer.Parse Integer.TryParse is used where if a element can not be converted Integer.TryParse returns False. Also note TryParse can be used in other numeric types along with DateTime.
Enumerable.All determines whether all elements of a sequence satisfy a condition. In this case all string values can be converted to Integer. If one or more can not be converted then All returns False.
Dim testValue As Integer
Dim stringArray = {"123", "45 6", "789"}
If stringArray.All(Function(input) Integer.TryParse(input, testValue)) Then
Console.WriteLine("Safe to perform conversion")
Else
Console.WriteLine("Not safe to perform conversion")
End If
If this type of assertion is needed often a function can be created and placed in a common code module.
Public Module ArrayHelpers
Public Function CanConvertToIntegerArray(sender() As String) As Boolean
Dim testValue As Integer
Return sender.All(Function(input) Integer.TryParse(input, testValue))
End Function
End Module
If this type of assertion is needed often a function can be created and placed in a common code module.
Public Module ArrayHelpers
Public Function CanConvertToIntegerArray(sender() As String) As Boolean
Dim testValue As Integer
Return sender.All(Function(input) Integer.TryParse(input, testValue))
End Function
End Module
Working example
Dim stringArray = {"123", "45 6", "789"}
If CanConvertToIntegerArray(stringArray) Then
Console.WriteLine("Safe to perform conversion")
Else
Console.WriteLine("Not safe to perform conversion")
End If
The function could also be written as a Language Extension
Public Module ArrayHelpers
<Runtime.CompilerServices.Extension>
Public Function CanConvertToIntArray(sender() As String) As Boolean
Dim testValue As Integer
Return sender.All(Function(input) Integer.TryParse(input, testValue))
End Function
End Module
Then used as shown here.
Dim stringArray = {"123", "45 6", "789"}
If stringArray.CanConvertToIntArray() Then
Console.WriteLine("Safe to perform conversion")
Else
Console.WriteLine("Not safe to perform conversion")
End If
Since Integer is not the only type that may be in an array language extensions can be created for all other numeric types a developer may work with, example, Decimal.
Public Module ArrayHelpers
<Runtime.CompilerServices.Extension>
Public Function CanConvertToDecimalArray(sender() As String) As Boolean
Dim testValue As Decimal
Return sender.All(Function(input) Decimal.TryParse(input, testValue))
End Function
End Module
Safely converting from String array to Integer array
There are two paths to take when converting a String array to an Integer array, perserve all elements by setting elements which can not be converted to Integer to a default value such as 0 while the other path is to create an Integer array with only the values which can be converted to Integers.
Preserve all elements
The first path, preserve all elements where non-integer values will have a default value, in this case 0.
Rather than creating a function this will be done with a function setup as a language extension method. This is done by creating a code module as shown below.
Public Module IntegerArrayExtensions
<Runtime.CompilerServices.Extension>
Public Function ToIntegerPreserveArray(sender() As String) As Integer()
Return Array.ConvertAll(sender,
Function(input)
Dim integerValue As Integer
Integer.TryParse(input, integerValue)
Return integerValue
End Function)
End Function
End Module
The magic happens using Array.ConvertAll, first parameter is the String array, second parameter is a function which uses Integer.TryParse where if an element can be converted the variable integerValue will contain the Integer value while on failure to convert a String element to Integer 0 is returned.
The follow code sample provides a string array with some elements which can not be converted to Integer values.
Public Class Form1
Private Sub exampleButton_Click(sender As Object, e As EventArgs) _
Handles exampleButton.Click
Dim stringArray As String() =
{
"2",
"4B",
Nothing,
"6",
"8A",
"",
"1.3",
"Karen",
"-1"
}
Dim integerArray = stringArray.ToIntegerPreserveArray()
For index As Integer = 0 To stringArray.Length - 1
Console.WriteLine(
$"Index: {index} String value: " &
$"'{stringArray(index)}' Converted too: {integerArray(index)}")
Next
End Sub
End Class
The results show the index, original string value and the converted value.
Index: 0 String value: '2' Converted too: 2
Index: 1 String value: '4B' Converted too: 0
Index: 2 String value: '' Converted too: 0
Index: 3 String value: '6' Converted too: 6
Index: 4 String value: '8A' Converted too: 0
Index: 5 String value: '' Converted too: 0
Index: 6 String value: '1.3' Converted too: 0
Index: 7 String value: 'Karen' Converted too: 0
Index: 8 String value: '-1' Converted too: -1
Taking a slightly different path on obtaining output which has nothing to do with the conversion process but shows how developers can use chaining of statements together as done in ToIntegerPreserveArray.
Dim integerArray = stringArray.ToIntegerPreserveArray()
integerArray.
ToList().
Select(Function(number, index) New With
{
.Index = index,
.Value = number
}).ToList().
ForEach(Sub(data)
Console.WriteLine(
$"Index: {data.Index} " &
$"Integer Value: {data.Value,2} " &
$"Original Value: '{stringArray(data.Index)}'")
End Sub)
Breaking down the code for displaying information on the converted array.
- Use ToList extension method to gain access to Select extension method.
- Using a function in the Select method use two parameters, the first parameter represents an Integer of a element in IntegerArray while the second parameter is the index of the element in the array.
- Within this function create an anonymous type, .Index represents the element index in the array while .Value represents the element which is an Integer.
- Using the language extension .ForEach iterate the anonymous type created in the function and display the same information shown in the prior code sample.
Shrink array to only true Integer values
In the prior example the resulting Integer array will be the exact size as the String array. In this section if a string element can not be converted to an Integer value those elements are discarded.
Consider reading a String array which has elements which are not Integer type and are consistant e.g. reading lines from a file.
1,Karen,Payne,1956
2,Bob,Jones,1977
3,Mary,Adams,1984
The task is to obtain the first element and the last element. In the code sample below the program reads a file named TextFile1.txt which resides in the same folder as the program's executable with the content shown above.
Using File.ReadAllLines which returns a string array of lines in the file where each item to read is delimited by a comma.
With a For-Each on each iteration a extension method, ToStringArray converts the string representing a line in the string array to a string array. This is followed by invoking ToIntegerArray which will return only integer values.
Public Class Form2
Private Sub exampleButton_Click(sender As Object, e As EventArgs) _
Handles exampleButton.Click
Dim fileName = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory, "TextFile1.txt")
Dim linesFromFile = File.ReadAllLines(fileName)
For Each line As String In linesFromFile
Dim items = line.ToStringArray().ToIntegerArray
Console.WriteLine($"Id: {items(0)}, Birth Year {items(1)}")
Next
End Sub
End Class
Here is the definition for ToStringArray which is a simple function which by default accepts a string which is split into a string array delimited by a comma which is defined as Optional meaning if the string is delimited by a comma the parameter need not be passed while if the string is delimited by another delimiter other than a comma the delimiter needs to be passed.
<Runtime.CompilerServices.Extension>
Public Function ToStringArray(
sender As String,
Optional separator As Char = ","c) As String()
Return sender.Split(separator)
End Function
Moving on to the language extension ToIntegerArray, to determine if an element can represent an Integer Integer.TryParse checks if the string can be converted when creating an instance of an anonymous type, .IsInteger stored the result of Integer.TryParse while .Value contains the result, if "Amy" is parsed .Value will contain 0 and .IsInteger will be False while a value of 12 will set .Value to 12 and .IsInteger to True.
In turn .Where condition specifies .IsInteger = True which in turn using .Select returns only Integer values.
<Runtime.CompilerServices.Extension>
Public Function ToIntegerArray(sender() As String) As Integer()
Return Array.ConvertAll(sender,
Function(input)
Dim value As Integer
Return New With
{
.IsInteger = Integer.TryParse(input, value),
.Value = value
}
End Function).
Where(Function(result) result.IsInteger).
Select(Function(result) result.Value).
ToArray()
End Function
Determining indexes which do not represent Integers
The following is an inversion of the above where the condition was .IsInteger = True this method does .IsInteger = False along with asking for the index rather than the actual value using GetNonIntegerIndexes.
<Runtime.CompilerServices.Extension>
Public Function GetNonIntegerIndexes(sender() As String) As Integer()
Return sender.Select(
Function(item, index)
Dim integerValue As Integer
Return If(Integer.TryParse(item, integerValue),
New With
{
.IsInteger = True,
.Index = index
},
New With
{
.IsInteger = False,
.Index = index
}
)
End Function).
ToArray().
Where(Function(item) item.IsInteger = False).
Select(Function(item) item.Index).
ToArray()
End Function
Code sample which rather than obtaining values for the first and last element will return the two middle elements which are first and last name indexes.
Public Class Form2
Private Sub exampleButton_Click(sender As Object, e As EventArgs) _
Handles exampleButton.Click
Dim fileName = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory, "TextFile1.txt")
Dim linesFromFile = File.ReadAllLines(fileName)
For Each line As String In linesFromFile
Dim items = line.ToStringArray().GetNonIntegerIndexes
Console.WriteLine($"First name: {items(0)}, Last name {items(1)}")
Next
End Sub
End Class
String array to Double array
The same logic which works for string to integer array applies to string to double arrays. For instance, to test if a string array can be converted to a string array use CanConvertToIntArray extension method, for testing if a string array can be converted to a double array use CanConvertToDoubleArray. To convert only integer elements in a string array to an integer array discarding non-integer elements ToIntegerArray, and for the same operation with string to double array use ToDoubleArray.
Suppose there is a need to convert a string array of currency values to a pure double array, the following langage extension, FromCurrencyToDoubleArray can perform this operation. The main difference between converting a string array with possible double values to a double array is in the array with currency symbols there is a need to tell TryParse (overload constructor) to indicate the number style and which currency to use. In the following the culture is set to the current culture. If there is a need to perform this conversion on other cultures and overloaded version can be created passing in the culture code, create the culture to perform the operation on the desired culture.
<Runtime.CompilerServices.Extension>
Public Function FromCurrencyToDoubleArray(sender() As String) As Double()
Return Array.ConvertAll(sender,
Function(input)
Dim value As Double
Return New With
{
.IsDDouble = Double.TryParse(
input, NumberStyles.Number Or NumberStyles.AllowCurrencySymbol,
CultureInfo.CurrentCulture, value),
.Value = value
}
End Function).
Where(Function(result) result.IsDDouble).
Select(Function(result) result.Value).
ToArray()
End Function
Miscellaneous extensions
The following are helper extensions which are wrappers for joining numeric arrays to a comma delimited string for assisting in either debugging purposes or when there is a need to create a text file were each line consist of a numeric array.
Namespace LanguageExtensions
Public Module GeneralHelperExtensions
<Runtime.CompilerServices.Extension>
Public Function IntegerArrayToString(sender() As Integer) As String
Return String.Join(",", sender)
End Function
<Runtime.CompilerServices.Extension>
Public Function DecimalArrayToString(sender() As Decimal) As String
Return String.Join(",", sender)
End Function
<Runtime.CompilerServices.Extension>
Public Function DoubleArrayToString(sender() As Double) As String
Return String.Join(",", sender)
End Function
<Runtime.CompilerServices.Extension>
Public Function ToStringArray(sender As String,
Optional separator As Char = ","c) As String()
Return sender.Split(separator)
End Function
End Module
End Namespace
Unit test
Unit test are critical when writing code especially when creating language extension methods as presented here. If all unit test pass then in a production application there is a failure where the issue might have something to do with the extension method then rerun the unit test to ensure something was not missed. If something was missed then fix the extension method along with writing new unit test to cover the issue which was not covered originally. If the issue was not from a homegrown extension method this makes it easy to disregard the method(s) and focus on other parts of code logic.
For instance, for the following generic extension method test should be created for all types which it may be used for.
<CompilerServices.Extension>
Public Function ToNullable(Of T As Structure)(sender As String) As T?
Dim result As New T?()
Try
If Not String.IsNullOrWhiteSpace(sender) Then
Dim converter As TypeConverter = TypeDescriptor.GetConverter(GetType(T))
result = CType(converter.ConvertFrom(sender), T)
End If
Catch
' don't care, caller should use HasValue before accessing the value.
End Try
Return result
End Function
Integer test
''' <summary>
''' Given a string array where some elements can be converted to
''' Nullable Integer and some elements which can not be converted
''' validate the extension method ToNullable
''' </summary>
<TestMethod>
Public Sub StringToNullableInteger_Successful()
Dim expected() As Integer? = {2, 6, -1}
Dim nullableResults =
(
From item In StringArrayMixedTypesIntegers
Select value = item.ToNullable(Of Integer)
Where value.HasValue
).ToList()
Assert.IsTrue(nullableResults.SequenceEqual(expected))
End Sub
Double test
''' <summary>
''' Given a string array where some elements can be converted to
''' Nullable Integer and some elements which can not be converted
''' validate the extension method ToNullable
''' </summary>
<TestMethod>
Public Sub StringToNullableDouble_Successful()
Dim expected() As Double? = {2.4, 6.7, -1}
Dim nullableResults =
(
From item In StringArrayMixedTypesDoubles
Select value = item.ToNullable(Of Double)
Where value.HasValue
).ToList()
Assert.IsTrue(nullableResults.SequenceEqual(expected))
End Sub
In some cases a failed test is worth it's weight in gold to validate a good test (one that is under the best conditions) actually is a valid test. For example, the following test although simple validate a good test as in this case the test is to see if a string value will fail converting to an Integer.
The test passes (when the string can't be converted) when an a FormatException is thrown validated by ExpectedException attribute testing for FormatException. The difficulty here is knowing what exception may be thrown as there may be more than one which means the developer must try and throw various exceptions against a method which the developer coded. Better to spend time while coding a solution then a embracing issue while the application is in production.
''' <summary>
''' Attempt to parse a string which can not be converted to an Integer.
''' No assertion requires pass ExpectedException attributive.
''' </summary>
<TestMethod, ExpectedException(GetType(FormatException),
"Integer.Parse expects an Integer, failed as expected.")>
Public Sub WhatHappensWhenValueIsNotIntegerNoFormatting_Integer()
Dim nonInteger = "A"
Dim result = Integer.Parse(nonInteger)
End Sub
See also
VB.NET: Decimal Places / Significant Figures
C#: Generic Type Parameters And Dynamic Types
References
Conversions Between Strings and Other Types (Visual Basic)
Type Conversion Functions (Visual Basic)
How to: Convert an Object to Another Type in Visual Basic
Implicit and Explicit Conversions (Visual Basic)
Summary
This article has presented language extension methods to provide consistency for converting strings and string arrays to numeric types which were designed for common task when writing solutions using Visual Basic in Visual Studio in a class project which can be used in your solutions. Also, how to test these extension methods using unit test methods.
Requires
Using these extension methods in your solutions
To use these extension methods, add the project NumericHelpers to your Visual Studio solution which targets .NET Framework 3.5 or higher. Open project property page for your project and the class project property page, if the Framework version differs change one of them so both match. Add a reference to your project for the NumericHelper project followed by compiling the solution to ensure the solution compiles properly. From here add a Import statement which references the class project and begin using the extension methods.
Source code
All source code can be found in the following GitHub repository.