496 lines
17 KiB
Markdown
496 lines
17 KiB
Markdown
|
# Convert data types using casting and conversion techniques
|
||
|
|
||
|
Take control of the data in your applications, knowing when to apply the
|
||
|
correct technique to change data types as needed.
|
||
|
|
||
|
### Learning objectives
|
||
|
|
||
|
Use the casting operator to cast a value into a different data type.
|
||
|
|
||
|
Use conversion methods to convert a value into a different data type.
|
||
|
|
||
|
Guard against the loss of data when performing a cast or conversion operation.
|
||
|
|
||
|
Use the `TryParse()` method to safely convert a string into a numeric data type.
|
||
|
|
||
|
## Introduction
|
||
|
|
||
|
Suppose you're a software developer on a team working on medical intake form
|
||
|
automation. You're tasked with delivery of the application features for
|
||
|
collecting data entered by a medical technician before the doctor sees the
|
||
|
patient. The technician can use the application to record the date and time,
|
||
|
patient age, height, weight, pulse, and blood pressure. The application also
|
||
|
provides text fields for other information, such as the reason for the visit,
|
||
|
current prescriptions, and other items. You work with many data that is in a
|
||
|
mix of data types. For the prototype, you'll build a console application and
|
||
|
collect all of the input as `strings`.
|
||
|
|
||
|
Because the input is initially input as a string, you need to occasionally
|
||
|
change values from one data type into another in the code. A simple example is
|
||
|
any mathematical operation you want to perform with string data. You would
|
||
|
first need to change the value into a numeric data type, like `int`, and then you
|
||
|
could manipulate the operation. Alternatively, you may want to format and
|
||
|
output a numeric value for a summary report using string interpolation.
|
||
|
|
||
|
You use different techniques to change a data type when necessary. You learn
|
||
|
when to use one technique over another, and when a given technique might risk
|
||
|
the loss of data.
|
||
|
|
||
|
By the end of this module, you're able to take control of the data in your
|
||
|
applications, knowing when to apply the correct technique to change data types
|
||
|
as needed.
|
||
|
|
||
|
### Learning objectives
|
||
|
|
||
|
In this module, you'll:
|
||
|
|
||
|
- Use the casting operator to cast a value into a different data type.
|
||
|
- Use conversion methods to convert a value into a different data type.
|
||
|
- Guard against the loss of data when performing a cast or conversion operation.
|
||
|
- Use the `TryParse()` method to safely convert a string into a numeric data
|
||
|
type.
|
||
|
|
||
|
---
|
||
|
|
||
|
## Exercise
|
||
|
|
||
|
### Explore data type casting and conversion
|
||
|
|
||
|
There are multiple techniques to perform a data type conversion. The technique
|
||
|
you choose depends on your answer to two important questions:
|
||
|
|
||
|
- Is it possible, depending on the value, that attempting to change the value's data type would throw an exception at run time?
|
||
|
- Is it possible, depending on the value, that attempting to change the value's data type would result in a loss of information?
|
||
|
In this exercise, you work your way through these questions, the implications of their answers, and which technique you should use when you need to change the data type.
|
||
|
|
||
|
#### Prepare your coding environment
|
||
|
|
||
|
Create a new console application. At the Terminal command prompt type:
|
||
|
`dotnet new console -o ./path_to/Project` or `dotnet new console -n "Project"`
|
||
|
and then press Enter.
|
||
|
|
||
|
This .NET CLI command uses a .NET program template to create a new C# console
|
||
|
application project in the specified folder location.
|
||
|
|
||
|
You use this C# console project to create, build, and run code samples during
|
||
|
this module.
|
||
|
|
||
|
Close the Terminal panel.
|
||
|
|
||
|
### Question
|
||
|
|
||
|
**Is it possible that attempting to change the value's data type would throw an
|
||
|
exception at run time?**
|
||
|
|
||
|
The C# compiler attempts to accommodate your code, but doesn't compile
|
||
|
operations that could result in an exception. When you understand the C#
|
||
|
compiler's primary concern, understanding why it functions a certain way is
|
||
|
easier.
|
||
|
|
||
|
#### Write code that attempts to add an `int` and a `string` and save the result in an `int`
|
||
|
|
||
|
Type the following code into the Visual Studio Code Editor:
|
||
|
|
||
|
```cs
|
||
|
int first = 2;
|
||
|
string second = "4";
|
||
|
int result = first + second;
|
||
|
Console.WriteLine(result);
|
||
|
```
|
||
|
|
||
|
Here, you're attempting to add the values `2` and `4`. The value `4` is of type
|
||
|
`string`. Will this work?
|
||
|
|
||
|
At the Terminal command prompt, to run your code, type `dotnet run` and then
|
||
|
press Enter.
|
||
|
|
||
|
You should see the following approximate output
|
||
|
|
||
|
Windows:
|
||
|
|
||
|
```txt
|
||
|
C:\Users\someuser\Documents\csharpprojects\TestProject\Program.cs(3,14): error CS0029: Cannot implicitly convert type 'string' to 'int'
|
||
|
```
|
||
|
|
||
|
Linux:
|
||
|
|
||
|
```txt
|
||
|
/home/user/Projects/TestProject/Program.cs(3,14): error CS0029: Cannot implicitly convert type 'string' to 'int'
|
||
|
```
|
||
|
|
||
|
> Note
|
||
|
> If you see a message saying "Couldn't find a project to run", ensure that the
|
||
|
Terminal command prompt displays the expected TestProject folder location. For
|
||
|
example: `C:\Users\someuser\Documents\csharpprojects\TestProject>`
|
||
|
|
||
|
Take a minute to consider why the compiler was unable to run the first code sample.
|
||
|
|
||
|
The important part of the error message,
|
||
|
`(3,14): error CS0029: Cannot implicitly convert type 'string' to 'int'`, tells
|
||
|
you the problem is with the use of the string data type.
|
||
|
|
||
|
But why can't the C# Compiler just handle the error? After all, you can do the
|
||
|
opposite to concatenate a number to a `string` and save it in a string variable.
|
||
|
Here, you change the data type of the `result` variable from `int` to string.
|
||
|
|
||
|
Update your code in the editor as follows:
|
||
|
|
||
|
```cs
|
||
|
int first = 2;
|
||
|
string second = "4";
|
||
|
string result = first + second;
|
||
|
Console.WriteLine(result);
|
||
|
```
|
||
|
|
||
|
Save your code file, and then run your code.
|
||
|
|
||
|
You should observe the following output:
|
||
|
|
||
|
```txt
|
||
|
24
|
||
|
```
|
||
|
|
||
|
The output is mathematically incorrect but completes by combining the values as
|
||
|
the characters "2" and "4".
|
||
|
|
||
|
Examine, once again, the first code example where the `result` variable is of
|
||
|
type `int`. The code with the error message.
|
||
|
|
||
|
```cs
|
||
|
int first = 2;
|
||
|
string second = "4";
|
||
|
int result = first + second;
|
||
|
Console.WriteLine(result);
|
||
|
```
|
||
|
|
||
|
Why can't the C# compiler figure out that you want to treat the variable `second`
|
||
|
containing `4` as a number, not a `string`?
|
||
|
|
||
|
### Compilers make safe conversions
|
||
|
|
||
|
The C# compiler sees a potential problem in the making. The variable `second` is
|
||
|
of type `string`, so it might be set to a different value like `"hello"`. If the
|
||
|
C# compiler attempted to convert "hello" to a number that would cause an
|
||
|
exception at runtime. To avoid this possibility, the C# compiler doesn't
|
||
|
implicitly perform the conversion from `string` to `int` for you.
|
||
|
|
||
|
From the C# compiler's perspective, the safer operation would be to convert
|
||
|
`int` into a `string` and perform concatenation instead.
|
||
|
|
||
|
If you intend to perform addition using a string, the C# compiler requires you
|
||
|
to take more explicit control of the process of data conversion. In other words,
|
||
|
it forces you to be more involved so that you can put the proper precautions in
|
||
|
place to handle the possibility that the conversion could throw an exception.
|
||
|
|
||
|
If you need to change a value from the original data type to a new data type
|
||
|
and the change could produce an exception at run time, you must perform a data
|
||
|
conversion.
|
||
|
|
||
|
To perform data conversion, you can use one of several techniques:
|
||
|
|
||
|
- Use a helper method on the data type
|
||
|
- Use a helper method on the variable
|
||
|
- Use the Convert class' methods
|
||
|
|
||
|
You look at a few examples of these techniques for data conversion later in
|
||
|
this unit.
|
||
|
|
||
|
### Question
|
||
|
|
||
|
**Is it possible that attempting to change the value's data type would result in
|
||
|
a loss of information?**
|
||
|
|
||
|
Delete or use the line comment operator `//` to comment out the code from the
|
||
|
previous exercise step, and add the following code:
|
||
|
|
||
|
```cs
|
||
|
int myInt = 3;
|
||
|
Console.WriteLine($"int: {myInt}");
|
||
|
|
||
|
decimal myDecimal = myInt;
|
||
|
Console.WriteLine($"decimal: {myDecimal}");
|
||
|
```
|
||
|
|
||
|
Save your code file, and then run your code.
|
||
|
|
||
|
You should see the following output:
|
||
|
|
||
|
```txt
|
||
|
int: 3
|
||
|
decimal: 3
|
||
|
```
|
||
|
|
||
|
The key to this example is this line of code:
|
||
|
|
||
|
```cs
|
||
|
decimal myDecimal = myInt;
|
||
|
```
|
||
|
|
||
|
Since any `int` value can easily fit inside of a `decimal`, the compiler performs
|
||
|
the conversion.
|
||
|
|
||
|
The term *widening* conversion means that you're attempting to convert a value
|
||
|
**from** a data type that could hold less information **to** a data type that
|
||
|
can hold *more* information. In this case, a value stored in a variable of type
|
||
|
`int` converted to a variable of type `decimal`, doesn't lose information.
|
||
|
|
||
|
When you know you're performing a widening conversion, you can rely on implicit
|
||
|
conversion. The compiler handles implicit conversions.
|
||
|
|
||
|
### Perform a cast
|
||
|
|
||
|
Delete or use the line comment operator `//` to comment out the code from the
|
||
|
previous exercise step, and add the following code:
|
||
|
|
||
|
```cs
|
||
|
decimal myDecimal = 3.14m;
|
||
|
Console.WriteLine($"decimal: {myDecimal}");
|
||
|
|
||
|
int myInt = (int)myDecimal;
|
||
|
Console.WriteLine($"int: {myInt}");
|
||
|
```
|
||
|
|
||
|
To perform a cast, you use the casting operator `()` to surround a data type,
|
||
|
then place it next to the variable you want to convert (example:
|
||
|
`(int)myDecimal`). You perform an explicit conversion to the defined cast data
|
||
|
type (`int`).
|
||
|
|
||
|
Save your file, and then run your code.
|
||
|
|
||
|
You should see the following output:
|
||
|
|
||
|
```txt
|
||
|
decimal: 3.14
|
||
|
int: 3
|
||
|
```
|
||
|
|
||
|
The key to this example is this line of code:
|
||
|
|
||
|
```cs
|
||
|
int myInt = (int)myDecimal;
|
||
|
```
|
||
|
|
||
|
The variable `myDecimal` holds a value that has precision after the decimal poin
|
||
|
t. By adding the casting instruction `(int)`, you're telling the C# compiler that
|
||
|
you understand it's possible you'll lose that precision, and in this situation,
|
||
|
it's fine. You're telling the compiler that you're performing an intentional
|
||
|
conversion, an explicit conversion.
|
||
|
|
||
|
### Determine if your conversion is a "widening conversion" or a "narrowing conversion"
|
||
|
|
||
|
The term narrowing conversion means that you're attempting to convert a value
|
||
|
from a data type that can hold more information to a data type that can hold
|
||
|
less information. In this case, you may lose information such as precision
|
||
|
(that is, the number of values after the decimal point). An example is
|
||
|
converting value stored in a variable of type `decimal` into a variable of type
|
||
|
`int`. If you print the two values, you would possibly notice the loss of
|
||
|
information.
|
||
|
|
||
|
When you know you're performing a narrowing conversion, you need to perform a
|
||
|
cast. Casting is an instruction to the C# compiler that you know precision may
|
||
|
be lost, but you're willing to accept it.
|
||
|
|
||
|
If you're unsure whether you lose data in the conversion, write code to perform
|
||
|
a conversion in two different ways and observe the changes. Developers
|
||
|
frequently write small tests to better understand the behaviors, as illustrated
|
||
|
with the next sample.
|
||
|
|
||
|
Delete or use the line comment operator `//` to comment out the code from the
|
||
|
previous exercise step, and add the following code:
|
||
|
|
||
|
```cs
|
||
|
decimal myDecimal = 1.23456789m;
|
||
|
float myFloat = (float)myDecimal;
|
||
|
|
||
|
Console.WriteLine($"Decimal: {myDecimal}");
|
||
|
Console.WriteLine($"Float : {myFloat}");
|
||
|
```
|
||
|
|
||
|
Save your file, and then run your code.
|
||
|
|
||
|
You should see output similar to:
|
||
|
|
||
|
```txt
|
||
|
Decimal: 1.23456789
|
||
|
Float : 1.2345679
|
||
|
```
|
||
|
|
||
|
You can observe from the output that casting a `decimal` into a `float` is a
|
||
|
narrowing conversion because you're losing precision.
|
||
|
|
||
|
### Performing Data Conversions
|
||
|
|
||
|
Earlier, it was stated that a value change from one data type into another
|
||
|
could cause a runtime exception, and you should perform data conversion. For
|
||
|
data conversions, there are three techniques you can use:
|
||
|
|
||
|
- Use a helper method on the variable
|
||
|
- Use a helper method on the data type
|
||
|
- Use the `Convert` class' methods
|
||
|
|
||
|
### Use `ToString()` to convert a number to a `string`
|
||
|
|
||
|
Every data type variable has a `ToString()` method. What the `ToString()` method
|
||
|
does depends on how it's implemented on a given type. However, on most
|
||
|
primitives, it performs a widening conversion. While this isn't strictly
|
||
|
necessary (since you can rely on implicit conversion in most cases) it can
|
||
|
communicate to other developers that you understand what you're doing and it's
|
||
|
intentional.
|
||
|
|
||
|
Here's a quick example of using the `ToString()` method to explicitly convert
|
||
|
`int` values into strings.
|
||
|
|
||
|
Delete or use the line comment operator `//` to comment out the code from the
|
||
|
previous exercise step, and add the following code:
|
||
|
|
||
|
```cs
|
||
|
int first = 5;
|
||
|
int second = 7;
|
||
|
string message = first.ToString() + second.ToString();
|
||
|
Console.WriteLine(message);
|
||
|
```
|
||
|
|
||
|
Save your file, and then run the code, the output should display a
|
||
|
concatenation of the two values:
|
||
|
|
||
|
```txt
|
||
|
57
|
||
|
```
|
||
|
|
||
|
### Convert a `string` to an `int` using the `Parse()` helper method
|
||
|
|
||
|
Most of the numeric data types have a `Parse()` method, which converts a
|
||
|
`string` into the given data type. In this case, you use the `Parse()` method
|
||
|
to convert two strings into int values, then add them together.
|
||
|
|
||
|
Delete or use the line comment operator `//` to comment out the code from the
|
||
|
previous exercise step, and add the following code:
|
||
|
|
||
|
```cs
|
||
|
string first = "5";
|
||
|
string second = "7";
|
||
|
int sum = int.Parse(first) + int.Parse(second);
|
||
|
Console.WriteLine(sum);
|
||
|
```
|
||
|
|
||
|
Save your file, and then run the code, the output should display a sum of the
|
||
|
two values:
|
||
|
|
||
|
```txt
|
||
|
12
|
||
|
```
|
||
|
|
||
|
Take a minute to try to spot the potential problem with the previous code
|
||
|
example. What if either of the variables `first` or `second` are set to values
|
||
|
that can't be converted to an `int`? An exception is thrown at runtime. The C#
|
||
|
compiler and runtime expects you to plan ahead and prevent "illegal"
|
||
|
conversions. You can mitigate the runtime exception in several ways.
|
||
|
|
||
|
The easiest way to mitigate this situation is by using `TryParse()`, which is a
|
||
|
better version of the `Parse()` method.
|
||
|
|
||
|
### Convert a `string` to a `int` using the `Convert` class
|
||
|
|
||
|
The `Convert` class has many helper methods to convert a value from one type
|
||
|
into another. In the following code example, you convert a couple of strings
|
||
|
into values of type `int`.
|
||
|
|
||
|
> Note
|
||
|
> The code samples in this exercise are designed based on en-US culture setting
|
||
|
s, and use a period (`.`) as the decimal separator. Building and running the
|
||
|
code with a culture setting that uses a different decimal separators (such as a
|
||
|
comma `,`) may give unexpected results or errors. To fix this issue, replace
|
||
|
the period decimal separators in the code samples with your local decimal
|
||
|
separator (such as `,`). Alternatively, to run a program using the en-US
|
||
|
culture setting, add the following code to the top of your program:
|
||
|
`using System.Globalization;` and after any other `using` statements add
|
||
|
`CultureInfo.CurrentCulture = new CultureInfo("en-US");`.
|
||
|
|
||
|
Delete or use the line comment operator `//` to comment out the code from the
|
||
|
previous exercise step, and add the following code:
|
||
|
|
||
|
```cs
|
||
|
string value1 = "5";
|
||
|
string value2 = "7";
|
||
|
int result = Convert.ToInt32(value1) * Convert.ToInt32(value2);
|
||
|
Console.WriteLine(result);
|
||
|
```
|
||
|
|
||
|
Save your file, and then run your code.
|
||
|
|
||
|
You should see the following output:
|
||
|
|
||
|
```txt
|
||
|
35
|
||
|
```
|
||
|
|
||
|
> Note
|
||
|
> Why is the method name `ToInt32()`? Why not `ToInt()`?
|
||
|
> `System.Int32` is the name of the underlying data type in the .NET Class
|
||
|
Library that the C# programming language maps to the keyword `int`. Because the
|
||
|
`Convert` class is also part of the .NET Class Library, it is called by its
|
||
|
full name, not its C# name. By defining data types as part of the .NET Class
|
||
|
Library, multiple .NET languages like Visual Basic, F#, IronPython, and others
|
||
|
can share the same data types and the same classes in the .NET Class Library.
|
||
|
|
||
|
The `ToInt32()` method has 19 overloaded versions allowing it to accept
|
||
|
virtually every data type.
|
||
|
|
||
|
you used the `Convert.ToInt32()` method with a string here, but you should
|
||
|
probably use `TryParse()` when possible.
|
||
|
|
||
|
So, when should you use the `Convert` class? The `Convert` class is best for
|
||
|
converting fractional numbers into whole numbers (`int`) because it rounds up
|
||
|
the way you would expect.
|
||
|
|
||
|
### Compare casting and converting a `decimal` into an `int`
|
||
|
|
||
|
The following example demonstrates what happens when you attempt to cast a
|
||
|
`decimal` into an `int` (a narrowing conversion) versus using the
|
||
|
`Convert.ToInt32()` method to convert the same `decimal` into an `int`.
|
||
|
|
||
|
Delete or use the line comment operator `//` to comment out the code from the
|
||
|
previous exercise step, and add the following code:
|
||
|
|
||
|
```cs
|
||
|
int value = (int)1.5m; // casting truncates
|
||
|
Console.WriteLine(value);
|
||
|
|
||
|
int value2 = Convert.ToInt32(1.5m); // converting rounds up
|
||
|
Console.WriteLine(value2);
|
||
|
```
|
||
|
|
||
|
Save your file, and then run your code.
|
||
|
|
||
|
You should see the following output:
|
||
|
|
||
|
```txt
|
||
|
1
|
||
|
2
|
||
|
```
|
||
|
|
||
|
#### Casting truncates and converting rounds
|
||
|
|
||
|
When you're casting `int value = (int)1.5m;`, the value of the float is truncated
|
||
|
so the result is `1`, meaning the value after the decimal is ignored completely.
|
||
|
You could change the literal float to `1.999m` and the result of casting would
|
||
|
be the same.
|
||
|
|
||
|
When you're converting using `Convert.ToInt32()`, the literal float value is
|
||
|
properly rounded up to `2`. If you changed the literal value to `1.499m`, it
|
||
|
would be rounded down to `1`.
|
||
|
|
||
|
### Recap
|
||
|
|
||
|
You covered several important concepts of data conversion and casting:
|
||
|
|
||
|
- Prevent a runtime error while performing a data conversion
|
||
|
- Perform an explicit cast to tell the compiler you understand the risk of
|
||
|
losing data
|
||
|
- Rely on the compiler to perform an implicit cast when performing an expanding
|
||
|
conversion
|
||
|
- Use the `()` cast operator and the data type to perform a cast (for example,
|
||
|
`(int)myDecimal`)
|
||
|
- Use the `Convert` class when you want to perform a narrowing conversion, but
|
||
|
want to perform rounding, not a truncation of information
|