C# Type Conversion

Type conversion means changing a value from one C# type to another, such as turning an int into a double or a text value like "42" into a number. It matters because C# is strongly typed: the compiler will not let unrelated types flow together unless a valid conversion exists. Good conversion habits prevent lost data, runtime exceptions, and confusing numeric results.

Overview: How Type Conversion Works

C# has several kinds of type conversion. An implicit conversion happens automatically when the compiler knows the conversion is safe. For example, an int can become a long, and a float can become a double, because the target type can represent at least the same general range or precision category. You do not write a cast for these conversions.

An explicit conversion requires a cast, such as (int)price. Casts are used when information may be lost, when overflow is possible, or when the compiler cannot prove the conversion is safe. Converting double to int discards the fractional part. Converting long to int may overflow if the value is outside the int range.

Text conversion is different from numeric casting. The string "123" is not a number stored as text that the compiler can cast. It must be parsed at runtime with methods such as int.Parse, int.TryParse, or Convert.ToInt32. Parsing can fail because user input, files, and network data can contain missing values, spaces, symbols, or culture-specific formatting.

The CLR, the runtime that executes C# programs, also supports reference conversions. A derived object can be used through a base class or interface reference. Going back from a base reference to a more specific type requires a cast or pattern matching. Value types can also be boxed into object, which copies the value into a heap object, and later unboxed back to its exact value type.

Conversions are not just syntax. They express a decision about meaning. A cast says you accept possible loss. A parse says text is expected to contain a value. A TryParse says invalid input is normal enough to handle without throwing an exception.

Syntax

int smallerInt = 42;
long bigger = smallerInt;
double decimalOrDouble = 12.75;
int whole = (int)decimalOrDouble;
string text = "123";
int parsed = int.Parse(text);
bool ok = int.TryParse(text, out int result);
object value = 9;
int converted = Convert.ToInt32(value);
object obj = "hello";
if (obj is string message) { string copy = message; }
Form Meaning
long bigger = smallerInt; Implicit numeric conversion. The compiler inserts it automatically.
(int)decimalOrDouble Explicit cast. It may lose data or throw in a checked context.
int.Parse(text) Converts valid numeric text to an int; throws if invalid.
int.TryParse(text, out int result) Attempts conversion and returns true or false instead of throwing.
Convert.ToInt32(value) Uses .NET conversion helpers for common built-in types.
obj is string message Checks a reference conversion and declares a typed variable when it succeeds.

Examples

Implicit Numeric Conversion

using System;

class Program
{
    static void Main()
    {
        int attendees = 125;
        long capacityCheck = attendees;
        double averagePerRoom = attendees / 4.0;

        Console.WriteLine($"Attendees: {attendees}");
        Console.WriteLine($"As long: {capacityCheck}");
        Console.WriteLine($"Average per room: {averagePerRoom}");
    }
}

Output:

Attendees: 125
As long: 125
Average per room: 31.25

The assignment from int to long is implicit because every possible int fits in a long. The division uses 4.0, a double literal, so C# converts attendees to double for that expression and keeps the fractional result.

Explicit Casts and Lost Fractions

using System;
using System.Globalization;

class Program
{
    static void Main()
    {
        double exactTotal = 19.95;
        int displayedDollars = (int)exactTotal;
        int roundedDollars = Convert.ToInt32(exactTotal);

        Console.WriteLine($"Exact: {exactTotal.ToString("F2", CultureInfo.InvariantCulture)}");
        Console.WriteLine($"Cast to int: {displayedDollars}");
        Console.WriteLine($"Convert.ToInt32: {roundedDollars}");
    }
}

Output:

Exact: 19.95
Cast to int: 19
Convert.ToInt32: 20

A cast from double to int truncates toward zero, so 19.95 becomes 19. Convert.ToInt32 rounds to the nearest integer using .NET’s standard rounding behavior. These operations answer different questions, so choose the one that matches the rule your program needs.

Parsing Text Safely

using System;

class Program
{
    static void Main()
    {
        string quantityText = "18";
        string discountText = "ten";

        int quantity = int.Parse(quantityText);

        if (int.TryParse(discountText, out int discountPercent))
        {
            Console.WriteLine($"Discount: {discountPercent}%");
        }
        else
        {
            Console.WriteLine("Discount was not a valid whole number.");
        }

        Console.WriteLine($"Quantity: {quantity}");
    }
}

Output:

Discount was not a valid whole number.
Quantity: 18

int.Parse is fine when the text is controlled by your program or already validated. TryParse is better for user input because invalid text is expected and should not usually crash the program. When TryParse returns false, the out variable still exists, but your code should treat it as unusable for the requested conversion.

Reference Conversion with Pattern Matching

using System;

class Program
{
    static void Main()
    {
        object value = "C#";

        if (value is string language)
        {
            Console.WriteLine(language.ToUpperInvariant());
            Console.WriteLine($"Length: {language.Length}");
        }
    }
}

Output:

C#
Length: 2

The variable value has compile-time type object, so the compiler only knows object members are available. The is string language pattern checks the runtime object and creates a strongly typed string variable inside the if block. This is safer and clearer than guessing with a direct cast.

How Type Conversion Works Step by Step

  1. The compiler assigns a compile-time type to every expression, variable, literal, and method result.
  2. When one type is used where another is required, the compiler searches for a valid implicit conversion. If it finds one, it emits the appropriate conversion instruction or treats the value as the target type.
  3. If only an explicit conversion exists, the compiler requires cast syntax. That cast tells future readers that the conversion may be narrowing, runtime-checked, or lossy.
  4. For parsing, the compiler does not convert the string at compile time. Runtime library code examines the characters and either returns a value or reports failure.
  5. For reference conversions, the CLR checks the actual runtime type of the object. Invalid casts throw InvalidCastException, while pattern matching simply evaluates to false.
  6. For boxing, the CLR copies a value type into an object wrapper. Unboxing must use the exact underlying value type, not merely a compatible numeric type.

Overflow checking is another detail to understand. By default, many integer casts in ordinary release code use an unchecked context, so a too-large value can wrap around. In a checked context, overflowing conversions throw OverflowException. Use checked when silently wrapping would corrupt important data.

Common Mistakes

Trying to Cast Numeric Text

string text = "42";
int number = (int)text;

This does not compile because a string is not a boxed integer or a numeric type. Parse the text instead.

string text = "42";
int number = int.Parse(text);
Console.WriteLine(number + 8);

Output:

50

Expecting a Cast to Round

using System;

class Program
{
    static void Main()
    {
        double temperature = -2.9;
        int castValue = (int)temperature;
        int roundedValue = (int)Math.Round(temperature);

        Console.WriteLine(castValue);
        Console.WriteLine(roundedValue);
    }
}

Output:

-2
-3

A numeric cast truncates toward zero. It does not round down, round up, or round to the nearest integer. If your program needs rounding, call Math.Round, Math.Floor, or Math.Ceiling explicitly.

Assigning Boxed Values Directly

object boxed = 10;
long value = boxed;

This does not compile because boxed has compile-time type object. The object contains a boxed int, so unbox to int first, then use the normal numeric conversion to long.

object boxed = 10;
int unboxed = (int)boxed;
long value = unboxed;
Console.WriteLine(value);

Output:

10

Best Practices

  • Prefer implicit conversions only when they are obvious and lossless.
  • Use explicit casts sparingly, and treat each cast as a place where data loss or runtime failure may occur.
  • Use TryParse for user input, files, query strings, and other external text.
  • Use Parse only when invalid text should be considered a programming error or has already been validated.
  • Do not cast strings to numbers. Parse strings; cast numeric values.
  • Use checked around narrowing integer conversions when overflow would be dangerous.
  • Use pattern matching such as is Type name for uncertain reference conversions.
  • Avoid unnecessary boxing by using generic collections such as List<int> instead of storing value types as object.
  • Be careful with money. Prefer decimal and explicit rounding rules over converting through double.

Practice Exercises

  1. Create an int variable for minutes and convert it to double hours by dividing by 60.0. Print both values.
  2. Write a program that receives two string variables, one valid number and one invalid number, and uses TryParse to decide which can be used in a calculation.
  3. Store a string in an object variable. Use pattern matching to check whether it is a string, then print its uppercase version.

Summary

  • C# conversion rules protect type safety while still allowing values to move between compatible types.
  • Implicit conversions are automatic and should be safe; explicit casts require syntax because they may lose data or fail.
  • Casting changes typed values. Parsing reads characters and creates a typed value from text.
  • TryParse is the normal choice for external or user-entered text because it avoids exception-driven control flow.
  • Reference casts are checked against the object’s runtime type; pattern matching is often the clearest safe form.
  • Boxing copies a value type into an object, and unboxing must use the exact original value type.
  • Choose conversions deliberately so your code states whether it is widening, narrowing, parsing, rounding, or checking a runtime type.