Type Casting

Learn to safely convert values between different data types using implicit and explicit casting techniques.

We'll cover the following...

In this lesson, we’ll explore the concept called type casting, where we convert an object of one type to an object of another type.

Consider the following example:

C# 14.0
byte age = 24;
int in10Years = age + 10;
Console.WriteLine(in10Years);
  • Line 1: Initializes a byte variable named age with a value of 24.

  • Line 2: Adds 10 to age. Since arithmetic operations on byte values result in an int, the result is stored in an int variable named in10Years.

  • Line 4: Prints the result, 34, to the console.

It’s safe to assume that the value of in10Years is 34. But, if we change the type of in10Years to byte, our code fails to compile:

C# 14.0
byte age = 24;
byte in10Years = age + 10; // Error
Console.WriteLine(in10Years);
  • Line 2: The compiler generates an error here. Even though 34 fits in a byte, the addition operation returns an int. We cannot implicitly assign an int to a byte because of potential data loss.

It fails because C# automatically promotes operands of types like byte and short to int before performing arithmetic operations. Because in10Years is now a byte, we can’t store an integer there automatically. To solve this problem, we cast the result of the arithmetic expression to the desired data type:

C# 14.0
byte age = 24;
// We cast the result of the expression to byte
byte in10Years = (byte)(age + 10);
Console.WriteLine(in10Years);
  • Line 4: We perform the addition age + 10. We then place (byte) before the result to explicitly convert the int back into a byte before assignment.

Type casting is performed by putting the target data type inside parentheses and placing the whole thing before an expression or variable that we want to cast.

Shrink

In the example above, we shrink an int value into a byte. In C# terminology, this is technically called a narrowing conversion. Although we won’t have any issues in the given code, shrinking can result in unexpected behavior. This is because we’re placing a value into an area of smaller size than the space the value occupies in memory:

byte age = 24;
byte someNumber = (byte)(age + 250);

The someNumber variable is a byte, and bytes can hold a whole number up to 255. Because 24 + 250 is 274, it’s too large for a byte to hold. Our typecast forces this value into the smaller container, producing an unexpected result:

C# 14.0
byte age = 24;
byte someNumber = (byte)(age + 250);
Console.WriteLine(someNumber);
  • Line 10: We add 250 to 24, resulting in 274. We explicitly cast this int to a byte.

  • Line 12: The output is 18, not 274.

Our operation leads to an overflow because 274 is too large for a byte to handle. The byte type wraps around after 255, and this modular arithmetic leads to data loss in the default compiler context.

Shrinking and careless arithmetic operations can lead to these overflows.

The following illustration helps visualize what this narrowing conversion looks like:

Data loss during shrinking
Data loss during shrinking

Note: C# does not have a native 4-bit integer type. This illustration conceptually shows how narrowing conversions discard higher-order bits when converting to a smaller data type (for example, from int to byte).

Expand

Casting to a data type that’s larger in size, technically called a widening conversion, is always safe:

Storing a smaller value in a larger container
Storing a smaller value in a larger container

Consider the following example:

C# 14.0
byte age = 24;
int ageAsInt = (int)age;
Console.WriteLine("Byte value: " + age);
Console.WriteLine("Int value: " + ageAsInt);
  • Line 2: The byte value is explicitly cast to an int. Since int is larger, no data is lost.

This operation is safe because int has enough space to contain all possible values of the byte type. The compiler performs these as implicit conversions, so they do not require specific syntax from the developer.

C# 14.0
byte age = 24;
int ageAsInt = age; // Implicit conversion occurred
Console.WriteLine("Byte value: " + age);
Console.WriteLine("Int value: " + ageAsInt);
  • Line 2: We assign age directly to ageAsInt without parentheses. The compiler handles this automatically because it is a safe widening conversion.

The compiler informs us if implicit conversion is impossible. In this case, we need to use explicit conversion by putting the target data type inside parentheses like (int)someVariable.

Understand that explicit type casting is possible for compatible data types. We can’t cast a string object to int using this method, for instance.