diff --git a/021_Casting_and_conversion_techniques/021_csharp.md b/021_Casting_and_conversion_techniques/021_csharp.md new file mode 100644 index 0000000..30fc68d --- /dev/null +++ b/021_Casting_and_conversion_techniques/021_csharp.md @@ -0,0 +1,495 @@ +# 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 diff --git a/021_Casting_and_conversion_techniques/test_app/Program.cs b/021_Casting_and_conversion_techniques/test_app/Program.cs new file mode 100644 index 0000000..9d6bcc0 --- /dev/null +++ b/021_Casting_and_conversion_techniques/test_app/Program.cs @@ -0,0 +1,65 @@ +// int first = 2; +// string second = "4"; +// int result = first + second; +// Console.WriteLine(result); +string sep = "----------------------------------"; + +int first = 2; +string second = "4"; +string result = first + second; +Console.WriteLine(result); + +Console.WriteLine(sep); + +int myInt = 3; +Console.WriteLine($"int: {myInt}"); + +decimal myDecimal = myInt; +Console.WriteLine($"decimal: {myDecimal}"); + +Console.WriteLine(sep); + +myDecimal = 3.14m; +Console.WriteLine($"decimal: {myDecimal}"); + +myInt = (int)myDecimal; +Console.WriteLine($"int: {myInt}"); + +Console.WriteLine(sep); + +myDecimal = 1.23456789m; +float myFloat = (float)myDecimal; + +Console.WriteLine($"Decimal: {myDecimal}"); +Console.WriteLine($"Float : {myFloat}"); + +Console.WriteLine(sep); + +// first = 5; +// second = 7; +// string message = first.ToString() + second.ToString(); +// Console.WriteLine(message); + +Console.WriteLine(sep); + +string first_str = "5"; +second = "7"; +int sum = int.Parse(first_str) + int.Parse(second); +Console.WriteLine(sum); + +Console.WriteLine(sep); + +string value1 = "5"; +string value2 = "7"; +int result_1 = Convert.ToInt32(value1) * Convert.ToInt32(value2); +Console.WriteLine(result_1); + +Console.WriteLine(sep); + +int value_1 = (int)1.5m; // casting truncates +Console.WriteLine(value_1); + +int value_2 = Convert.ToInt32(1.5m); // converting rounds up +Console.WriteLine(value_2); + +Console.WriteLine(sep); diff --git a/021_Casting_and_conversion_techniques/test_app/test_app.csproj b/021_Casting_and_conversion_techniques/test_app/test_app.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/021_Casting_and_conversion_techniques/test_app/test_app.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/README.md b/README.md index dd62589..baa72f8 100644 --- a/README.md +++ b/README.md @@ -5,23 +5,24 @@ Following ## Foundational C# with Microsoft -- [First Code](./001_first_code/001_csharp.md) -- [Store and retrieve data](./002_Store_retrieve_data_using_literal_and_variable/002_csharp.md) -- [Basic String Formating](./003_Basic_string_formatting/003_csharp.md) -- [Basic operations on numbers](./004_Basic_operation_on_numbers/004_csharp.md) -- [Calculate and print student grades](./005_Calc_and_print_grades/005_csharp.md) -- [Calculate students final GPA](./006_Calc_final_GPA/006_csharp.md) -- [Call methods from the .NET Class Library](./007_Methods_net_class/007_csharp.md) -- [if and if else](./008_if_else/008_csharp.md) -- [array and foreach](./009_arrays_forearc/009_csharp.md) -- [Conventions](./010_conventions/010_csharp.md) -- [Guided Project - foreach](./011_foreach_array_1/011_csharp.md) -- [Challenge Project - foreach](./012_foreach_array_2/012_csharp.md) -- [Evaluate Boolean expressions](/013_eval_boolean_expressions/013_csharp.md) -- [Control variable scope and logic](/014_scope_and_logic/014_csharp.md) -- [switch case construct](./015_switch_case/015_csharp.md) -- [FizzBuzz Challenge](./016_Iterate_using_for_statement/016_csharp.md) -- [Looping logic using the do-while and while](./017_Looping_logic_using_do-while_and_while/017_csharp.md) -- [Conditional branching and looping](./018_Conditional_branching_and_looping/018_csharp.md) -- [Branching and looping structures](./019_branching_and_looping_structures/019_csharp.md) -- [Choose the correct data type](/020_data_types/020_csharp.md) +1. [First Code](./001_first_code/001_csharp.md) +2. [Store and retrieve data](./002_Store_retrieve_data_using_literal_and_variable/002_csharp.md) +3. [Basic String Formating](./003_Basic_string_formatting/003_csharp.md) +4. [Basic operations on numbers](./004_Basic_operation_on_numbers/004_csharp.md) +5. [Calculate and print student grades](./005_Calc_and_print_grades/005_csharp.md) +6. [Calculate students final GPA](./006_Calc_final_GPA/006_csharp.md) +7. [Call methods from the .NET Class Library](./007_Methods_net_class/007_csharp.md) +8. [if and if else](./008_if_else/008_csharp.md) +9. [array and foreach](./009_arrays_forearc/009_csharp.md) +10. [Conventions](./010_conventions/010_csharp.md) +11. [Guided Project - foreach](./011_foreach_array_1/011_csharp.md) +12. [Challenge Project - foreach](./012_foreach_array_2/012_csharp.md) +13. [Evaluate Boolean expressions](/013_eval_boolean_expressions/013_csharp.md) +14. [Control variable scope and logic](/014_scope_and_logic/014_csharp.md) +15. [switch case construct](./015_switch_case/015_csharp.md) +16. [FizzBuzz Challenge](./016_Iterate_using_for_statement/016_csharp.md) +17. [Looping logic using the do-while and while](./017_Looping_logic_using_do-while_and_while/017_csharp.md) +18. [Conditional branching and looping](./018_Conditional_branching_and_looping/018_csharp.md) +19. [Branching and looping structures](./019_branching_and_looping_structures/019_csharp.md) +20. [Choose the correct data type](./020_data_types/020_csharp.md) +21. [Convert data types](./021_Casting_and_conversion_techniques/021_csharp.md)