← Open in the full interactive course (progress tracking, search & more)

Changing data from one type to another — safely, explicitly, and efficiently.

Imagine a user enters their age into a text box on a registration form.

Your application receives "29" — but that's a string, not a number. To calculate their birth year, you need to treat it as an integer. You need a way to convert "29" into 29.

This is the essence of type conversion: changing a value from one data type to another so you can work with it in a different way. It happens everywhere — reading user input, parsing JSON, calculating totals, formatting output, and storing data.

Understanding type conversion deeply helps you avoid runtime crashes, data corruption, and subtle bugs.

What Is It?

The Simple Explanation

Type conversion means taking a value of one type and producing a corresponding value of another type.

The Technical Definition

In C#, type conversion is the process of converting a value from one data type (source type) to another data type (target type). C# is a statically typed language, so the compiler enforces type compatibility at compile time. Conversions can be:

Conversion Type How It's Done Safety Example
Implicit Compiler inserts automatically No data loss int x = 5; double y = x;
Explicit Developer writes cast (type) May lose data or throw double d = 5.7; int i = (int)d;
Parsing Method like Parse, TryParse Can throw on invalid input int n = int.Parse("29");
User-defined implicit/explicit operator Depends on implementation Money m = (Money)100.0m;

Why Does It Exist?

The Problem

C# is statically typed, meaning every variable has a fixed type at compile time. But real-world data comes in different shapes:

If types never changed, you couldn't read a number from a text box and add it to another number. You'd be stuck.

The Solution

Type conversion provides the bridge between different type systems. It allows you to:

The key insight

C# does not silently guess conversions that might lose data. It forces you to be explicit when a conversion could fail or lose precision. This design prevents accidental bugs but requires you to understand the rules.

Big Picture

Here's a visual mental model of how conversions flow in C#:

TYPE CONVERSION MAP
SOURCE TYPE
string, int, double, object, custom types...
CONVERSION MECHANISM
  • Implicit (compiler auto)
  • Explicit cast (type)
  • Parse / TryParse
  • Convert helper
  • User-defined operators
TARGET TYPE
The type you need for your operation.

How It Works

Let's trace what happens when you perform different kinds of conversions.

Step 1 — Implicit conversion (safe widening)

int count = 10;
long bigCount = count;   // implicit: int → long
double precise = count;  // implicit: int → double

The compiler sees that long and double can represent all possible int values exactly (or with acceptable precision for double). It automatically inserts the conversion without requiring a cast.

Step 2 — Explicit conversion (potential data loss)

double price = 19.99;
int wholePrice = (int)price;   // explicit: 19, fractional part lost

This is a narrowing conversion. The compiler cannot guarantee safety, so it requires you to write the cast (int). You accept the risk that data will be lost.

Step 3 — Parsing string to number

string input = "42";
int age = int.Parse(input);      // throws FormatException if input is not numeric

Parse reads the string and attempts to produce a number. If the string is not a valid number, it throws an exception. A safer alternative is TryParse:

if (int.TryParse(input, out int age2))
{
    Console.WriteLine($"Age is {age2}");
}
else
{
    Console.WriteLine("Invalid age");
}

TryParse returns bool and never throws. It's the preferred method for user input.

Step 4 — Using Convert class

object obj = 123;
int number = Convert.ToInt32(obj);   // handles null and IConvertible

Convert is a static helper class that provides conversions between many types. It can handle null by returning default values for value types (e.g., 0 for int). It's useful when the source type is object or string.

Step 5 — User-defined conversion

public readonly struct Temperature
{
    public double Celsius { get; }
    public Temperature(double celsius) => Celsius = celsius;
    public static implicit operator Temperature(double c) => new(c);
    public static explicit operator double(Temperature t) => t.Celsius;
}

Temperature temp = 36.6;       // implicit
double c = (double)temp;       // explicit

This is advanced but powerful. It lets you define how your own types convert to and from others.

Simple Example

using System;

string input = "15";

// Convert string to int using TryParse (safe)
if (int.TryParse(input, out int quantity))
{
    double unitPrice = 2.50;
    double total = quantity * unitPrice;   // int implicitly converts to double
    Console.WriteLine($"Total: {total:C}");  // $37.50
}
else
{
    Console.WriteLine("Invalid quantity");
}

// Explicit narrowing example
double average = 7.9;
int floor = (int)average;      // 7
Console.WriteLine(floor);

Code → Meaning → Result

Real-World Example

Consider an e-commerce API that receives an order as JSON. The JSON string contains numbers as text:

{
  "orderId": "1024",
  "total": "249.99"
}

Your C# code must convert these strings into appropriate types for processing:

string orderIdJson = "1024";
string totalJson = "249.99";

if (int.TryParse(orderIdJson, out int orderId)
    && decimal.TryParse(totalJson, System.Globalization.NumberStyles.Currency,
                        System.Globalization.CultureInfo.InvariantCulture,
                        out decimal total))
{
    // Now we can do arithmetic safely
    decimal tax = total * 0.08m;
    decimal grandTotal = total + tax;
    Console.WriteLine($"Order {orderId}: {grandTotal:C}");  // Order 1024: $269.99
}

Why TryParse here? Because JSON data is untrusted and may be malformed. TryParse lets us handle errors without throwing exceptions. We also specify InvariantCulture to ensure the decimal point is interpreted correctly regardless of server locale.

Analogy

Containers and Liquids

Implicit conversion — like pouring a small glass of water into a large bucket. It always fits; no risk.

Explicit conversion — like pouring a large bucket of water into a small glass. It might overflow, and you must decide to do it.

Parsing — like reading a label that says "500 ml" and then actually measuring out 500 ml of water. The label is text; the measurement is a numeric quantity.

Convert class — a multi-tool that can handle many different container shapes, sometimes with built-in safety (null handling).

This analogy captures the key idea: conversions have different levels of risk, and C# makes you acknowledge risky ones.

Under the Hood

What happens inside the CLR and compiler when you convert types?

INTERNAL VIEW
1. COMPILE-TIME CHECK
2. RUNTIME EXECUTION
3. BOXING AND UNBOXING
4. CHECKED AND UNCHECKED CONTEXT
5. MODERN .NET TYPES

Common Confusion

1. Implicit vs Explicit

Many beginners think all conversions are the same. The difference is:

2. Convert vs Parse vs TryParse

MethodThrows on invalid?Handles null?Use case
int.Parse("5")Yes (FormatException)NoKnown valid input
int.TryParse("5", out _)NoNo (returns false)User input, untrusted data
Convert.ToInt32(value)SometimesYes (returns default)Source type may be object/null

3. Casting vs as

For reference types, as operator attempts a conversion and returns null if it fails, whereas a cast throws InvalidCastException.

object obj = "hello";
string? s = obj as string;     // safe, returns string or null
if (s is not null) { ... }

string s2 = (string)obj;       // throws if obj is not string

4. Boxing vs Unboxing

Boxing is implicit when a value type is assigned to object. Unboxing is explicit and must match the exact original type.

int x = 42;
object boxed = x;          // boxing
int y = (int)boxed;        // unboxing to exact type

5. Culture and Parsing

Parsing numbers and dates depends on culture. In some cultures, "1,234.56" uses comma as thousands separator; in others, comma is decimal separator. Always specify culture or use InvariantCulture for data from external systems.

Common Mistakes

Mistake 1 — Using Parse on untrusted input

Wrong: int age = int.Parse(userInput); — if user enters "abc", the app crashes.

Correct: Use int.TryParse(userInput, out int age) and handle failure.

Mistake 2 — Relying on default culture for data parsing/formatting

decimal.Parse("1.99") may fail on a German system because dot is not the decimal separator.

Use CultureInfo.InvariantCulture for data interchange: decimal.Parse("1.99", CultureInfo.InvariantCulture).

Mistake 3 — Ignoring overflow and precision loss

long big = long.MaxValue; int small = (int)big; — overflows silently, produces -1.

Use checked context to detect overflow: int small = checked((int)big); or checked { int small = (int)big; }.

Mistake 4 — Unboxing to the wrong type

object obj = 123.45; int x = (int)obj; — throws InvalidCastException because the original was double.

Unbox to the exact original type first, then convert: int x = (int)(double)obj;.

Mistake 5 — Using Convert where TryParse is better

Convert.ToInt32 throws if the string is invalid or if the value is too large. For user input, TryParse is safer and faster (no exception throwing).

When Should I Use It?

Use implicit conversion when:

Use explicit cast when:

Use Parse / TryParse when:

Use Convert class when:

When conversion might be unnecessary:

Mental Model

Source Type = the data you have
Target Type = the data you need
Implicit = safe widening, compiler handles it
Explicit = risky narrowing, you must opt in
Parse / TryParse = reading text into a value
Convert = Swiss Army knife for object/null

Remember:
· Always use TryParse for user input
· Know your culture for parsing/formatting
· Use checked when overflow matters
· Unbox to the exact original type first

Key Takeaway


Check Your Understanding

You've seen how type conversion works, why explicit and implicit matter, and how to parse safely. Let's see if you can apply this knowledge.

1. Which of the following conversions is implicit in C#?

Show answer

Correct: B

Why B is correct: int to long is a widening conversion: every int value can be represented exactly as a long. The compiler performs this conversion automatically without requiring a cast.

Why A is incorrect: double to int is narrowing; it loses the fractional part, so C# requires an explicit cast.

Why C is incorrect: Unboxing from object to int is explicit; it can throw if the original type isn't exactly int.

Why D is incorrect: Parsing is explicit by definition — you call a method to convert text to a number; it's not automatic.

Reinforcement: Implicit conversions are always safe widening conversions. If there's any potential for data loss or failure, C# requires you to be explicit.

2. You're reading a user's input from a text box. Which code correctly and safely attempts to convert it to an integer without crashing on invalid input?

Show answer

Correct: C

Why C is correct: TryParse returns false if the input is not a valid integer, and never throws an exception. This is the safest way to handle user input.

Why A is incorrect: int.Parse throws a FormatException if the input is invalid, which can crash your app.

Why B is incorrect: Convert.ToInt32 also throws exceptions for invalid input (e.g., FormatException) and is less clear for untrusted user input.

Why D is incorrect: You cannot cast a string to int using a cast operator; this would not compile.

Reinforcement: For any external input (user, file, network), TryParse is the best practice because it avoids exceptions and gives you a boolean success flag.

3. What is the result of the following code?

double d = 9.99;
int x = (int)d;
Console.WriteLine(x);
Show answer

Correct: A

Why A is correct: The explicit cast from double to int truncates the fractional part; it does not round. So 9.99 becomes 9.

Why B is incorrect: Casting does not round to the nearest integer; it always truncates toward zero.

Why C is incorrect: The result is an int, so decimal places are lost.

Why D is incorrect: This conversion is valid and will not throw an exception (unless in a checked context and the value exceeds int.MaxValue, but 9.99 does not).

Reinforcement: When converting floating-point to integer, C# truncates toward zero. If you want rounding, use Math.Round or Convert.ToInt32 (which rounds to nearest even).

4. Why might decimal.Parse("1.99") fail on a machine with German culture settings?

Show answer

Correct: B

Why B is correct: Parsing numbers is culture-sensitive. In German (de-DE) culture, the decimal separator is , (comma), not . (period). Therefore "1.99" is not a valid decimal in that culture, and Parse will throw a FormatException.

Why A is incorrect: decimal.Parse works in all cultures; it uses the current culture by default, but you can specify a culture or InvariantCulture.

Why C is incorrect: decimal is specifically designed to represent fractional values with high precision.

Why D is incorrect: C# strings are not null-terminated in the same way as C strings; this is irrelevant to parsing.

Reinforcement: Always specify CultureInfo.InvariantCulture when parsing data from external systems (JSON, CSV, databases) to avoid environment-specific failures.

5. Which statement correctly describes boxing?

Show answer

Correct: A

Why A is correct: Boxing is the process of copying a value type (e.g., int) into a new object on the managed heap. It is implicit in C#.

Why B is incorrect: That describes unboxing, not boxing. Unboxing is explicit and requires the exact original type.

Why C is incorrect: Converting a string to an integer is parsing, not boxing.

Why D is incorrect: Boxing happens at runtime and allocates memory, which can impact performance in tight loops. It is not free.

Reinforcement: Boxing and unboxing are special conversions between value types and object. Avoid excessive boxing in performance-sensitive code by using generics.

You now understand type conversion — the bridge between data types that keeps your applications flexible and safe.


dotnetmadeeasy.com — Learn C# and .NET, the right way.