In programming, it is common to assign one type of variable to another. For example, you might want to assign an int value to a float variable, as shown here:
int i; float f; i = 10; f = i; // assign an int to a float
When compatible types are mixed in an assignment, the value of the right side is automatically converted to the type of the left side. Thus, in the preceding fragment, the value in i is converted into a float and then assigned to f. However, because of C#’s strict type-checking, not all types are compatible, and thus, not all type conversions are implicitly allowed. For example, bool and int are not compatible. Fortunately, it is still possible to obtain a conversion between incompatible types by using a cast. A cast performs an explicit type conversion. Both automatic type conversion and casting are examined here
Type conversion (typecasting) can be explicit and implicit. All expressions in C# have a type. This type can derive from the expression structure and the types, variables and literals used in it. It is possible to write an expression which type is inappropriate for the current context. In some cases this will lead to a compilation error, but in other cases the context can get a type that is similar or related to the type of the expression. This case the program performs a hidden type conversion.
In C# not all types can be converted to all other types, but only to some of them. For convenience, we shall group some of the possible transformations in C# according to their type into three categories:
- implicit conversion;
- explicit conversion;
Implicit Type Conversion
Implicit (hidden) type conversion is possible only when there is no risk of data loss during the conversion, i.e. when converting from a lower range type to a larger range (e.g. from int to long). To make an implicit conversion it is not necessary to use any operator and therefore such transformation is called implicit. The implicit conversion is done automatically by the compiler when you assign a value with lower range to a variable with larger range or if the expression has several types with different ranges. In such case the conversion is executed into the type with the highest range.
int myInt = 5; Console.WriteLine(myInt); // 5 long myLong = myInt; Console.WriteLine(myLong); // 5 Console.WriteLine(myLong + myInt); // 10
In the example we create a variable myInt of type int and assign it the value 5. After that we create a variable myLong of type long and assign it the value contained in myInt. The value stored in myLong is automatically converted from type int to type long. Finally, we output the result from adding the two variables. Because the variables are from different types they are automatically converted to the type with the greater range, i.e. to type long and the result that is printed on the console is long again. Indeed, the given parameter to the method Console.WriteLine() is of type long, but inside the method it will be converted again, this time to type string, so it can be printed on the console. This transformation is performed by the method Long.ToString().
Possible Implicit Conversions
Here are some possible implicit conversions of primitive data types in C#:
- sbyte → short, int, long, float, double, decimal;
- byte → short, ushort, int, uint, long, ulong, float, double, decimal;
- short → int, long, float, double, decimal;
- ushort → int, uint, long, ulong, float, double, decimal;
- char → ushort, int, uint, long, ulong, float, double, decimal (although char is a character type in some cases it may be regarded as a number and have a numeric type of behavior, it can even participate in numeric expressions);
- uint → long, ulong, float, double, decimal;
- int → long, float, double, decimal;
- long → float, double, decimal;
- ulong → float, double, decimal;
- float → double.
There is no data loss when converting types of smaller range to types with a larger range. The numerical value remains the same after conversion. There are a few exceptions. When you convert type int to type float (32-bit values), the difference is that int uses all bits for a whole number, whereas float has a part of bits used for representation of a fractional part. Hence, loss of precision is possible because of rounding when conversion from int to float is made. The same applies for the conversion of 64-bit long to 64-bit double.
If you know that a value is of a specific type, you can explicitly cast it to that type in order to use it in a context where that type is needed.
object value = -1; int number = (int) value; Console.WriteLine(Math.Abs(number));
If we tried passing value directly to Math.Abs(), we would get a compile-time exception because Math.Abs() doesn’t have an overload that takes an object as a parameter.
If value could not be cast to an int, then the second line in this example would throw an InvalidCastException
double myDouble = 5.1d; Console.WriteLine(myDouble); // 5.1 long myLong = (long)myDouble; Console.WriteLine(myLong); // 5 myDouble = 5e9d; // 5 * 10^9 Console.WriteLine(myDouble); // 5000000000 int myInt = (int)myDouble; Console.WriteLine(myInt); // -2147483648 Console.WriteLine(int.MinValue); // -2147483648
In the first line of the example we assign a value 5.1 to the variable myDouble. After we convert (explicitly) to type long using the operator (long) and print on the console the variable myLong we see that the variable has lost its fractional part, because long is an integer. Then we assign to the real double precision variable myDouble the value 5 billion. Finally, we convert myDouble to int by the operator (int) and print variable myInt. The result is the same like when we print int.MinValue because myDouble contains a value bigger than the range of int.