Book Image

Learn C# Programming

By : Marius Bancila, Raffaele Rialdi, Ankit Sharma
5 (1)
Book Image

Learn C# Programming

5 (1)
By: Marius Bancila, Raffaele Rialdi, Ankit Sharma

Overview of this book

The C# programming language is often developers’ primary choice for creating a wide range of applications for desktop, cloud, and mobile. In nearly two decades of its existence, C# has evolved from a general-purpose, object-oriented language to a multi-paradigm language with impressive features. This book will take you through C# from the ground up in a step-by-step manner. You'll start with the building blocks of C#, which include basic data types, variables, strings, arrays, operators, control statements, and loops. Once comfortable with the basics, you'll then progress to learning object-oriented programming concepts such as classes and structures, objects, interfaces, and abstraction. Generics, functional programming, dynamic, and asynchronous programming are covered in detail. This book also takes you through regular expressions, reflection, memory management, pattern matching, exceptions, and many other advanced topics. As you advance, you'll explore the .NET Core 3 framework and learn how to use the dotnet command-line interface (CLI), consume NuGet packages, develop for Linux, and migrate apps built with .NET Framework. Finally, you'll understand how to run unit tests with the Microsoft unit testing frameworks available in Visual Studio. By the end of this book, you’ll be well-versed with the essentials of the C# language and be ready to start creating apps with it.
Table of Contents (20 chapters)

Type conversion

Sometimes we need to convert one data type into another, and that is where type conversion comes in picture. Type conversion can be classified into several categories:

  • Implicit type conversion
  • Explicit type conversion
  • User-defined conversions
  • Conversions with helper classes

Let's explore these in detail.

Implicit type conversion

For built-in numeric types, when we assign the value of a variable to one of another data type, implicit type conversion occurs if both types are compatible and the range of destination type is more than that of the source type. For example, int and float are compatible types. Therefore, we can assign an integer variable to a variable of the float type. Similarly, the double type is large enough to hold values from any other numerical type, including long and float, as shown in the following example:

int i = 10;
float f = i;
long l = 7195467872;
double d = l;

The following table shows the implicit type conversion between numeric types in C#:

There are several things to note about implicit numeric conversions:

  • You can convert any integral type to any floating-point type.
  • There is no implicit conversion to the char, byte, and sbyte types.
  • There is no implicit conversion from double and decimal; this includes no implicit conversion from decimal to double or float.

For reference types, the implicit conversion is always possible between a class and one of its direct or indirect base classes or interfaces. Here is an example with an implicit conversion from string to object:

string s = "example";
object o = s;

The object type (which is an alias for System.Object) is the base class for all .NET types, including string (which is an alias for System.String). Therefore, an implicit conversion from string into object exists.

Explicit type conversion

When an implicit conversion between two types is not possible because there is a risk of losing information (such as while assigning the value of a 32-bit integer to a 16-bit integer), explicit type conversion is necessary. Explicit type conversion is also called a cast. To perform casting, we need to specify the target data type in parentheses in front of the source variable.

For example, double and int are incompatible types. Consequently, we need to do an explicit type conversion between them. In the following example, we assign a double value (d) to an integer using explicit type conversion. However, while doing this conversion, the fractional part of the double variable will be truncated. Hence, the value of i will be 12:

double d = 12.34;
int i = (int)d;

The following table shows the list of predefined explicit conversions between numeric types in C#:

There are several things to note about explicit numeric conversions:

  • An explicit conversion may result in precision loss or in throwing an exception, such as OverflowException.
  • When converting from an integral type to another integral type, the result depends on the so-called checked context and may result either in a successful conversion, which may discard extra most-significant bytes, or in an overflow exception.
  • When you convert a floating-point type to an integral type, the value is rounded toward zero to the nearest integral value. The operation may, however, also result in an overflow exception.

C# statements can execute either in a checked or unchecked context, which is control either with the check and unchecked keywords or with the compiler option, -checked. When none of these are specified, the context is considered unchecked for non-constant expressions. For constant expressions, which can be evaluated at compile time, the default context is always checked. In a checked context, overflow checking is enabled for integral-type arithmetic operations and conversions. In an unchecked context, these checks are suppressed. When overflow checking is enabled and overflow occurs, the runtime throws a System.OverflowException exception.

For reference types, an explicit cast is required when you want to convert from a base class or interface into a derived class. The following example shows a cast from an object to a string value:

string s = "example";
object o = s;          // implicit conversion
string r = (string)o;  // explicit conversion

The conversion from string into object is performed implicitly. However, the opposite requires an explicit conversion in the (string)o form, as shown in the preceding snippet.

User-defined type conversions

A user-defined conversion can define an implicit or explicit conversion or both from one type into another. The type that defines these conversions must be either the source or the target type. To do so, you must use the operator keyword followed by implicit or explicit. The following example shows a type called fancyint, which defines implicit and explicit conversions from and to int:

public readonly struct fancyint
{
    private readonly int value;
    public fancyint(int value)
    {
        this.value = value;
    }
    public static implicit operator int(fancyint v) => v.value;
    public static explicit operator fancyint(int v) => new fancyint(v);
    public override string ToString() => $"{value}";
}

You can use this type as follows:

fancyint a = new fancyint(42);
int i = a;                 // implicit conversion
fancyint b = (fancyint)i;  // explicit conversion

In this example, a is an object of the fancyint type. The value of a can be implicitly converted into int, because an implicit conversion operator is defined. However, the conversion from int to fancyint is defined as explicit, therefore a cast is necessary, as in (fancyint)i.

Conversions with helper classes

Conversion with a helper class or method is useful to convert between incompatible types, such as between a string and an integer or a System.DateTime object. There are various helper classes provided by the framework, such as the System.BitConverter class, the System.Convert class, and the Parse() and TryParse() methods of the built-in numeric types. However, you can provide your own classes and methods to convert between any types.

The following listing shows several examples of conversion using helper classes:

DateTime dt1 = DateTime.Parse("2019.08.31");
DateTime.TryParse("2019.08.31", out DateTime dt2);
int i1 = int.Parse("42");          // successful, i1 = 42
int i2 = int.Parse("42.15");       // error, throws exception
int.TryParse("42.15", out int i3); // error, returns false, 
                                   // i3 = 0

It is important to note the key difference between Parse() and TryParse(). The former tries to perform parsing and if that succeeds, it returns the parsed value; but if it fails, it throws an exception. The latter does not throw an exception, but returns bool, indicating the success or failure, and sets the second out parameter to the parsed value if successful or to the default value if it fails.