C# Type Conversion: Casting, Parse, TryParse and Convert
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:
- Implicit — done automatically by the compiler when no data loss is possible.
- Explicit (casting) — done by the developer using
(targetType)or operators; may lose data or throw exceptions. - Parsing — converting a string representation into another type (e.g.,
int.Parse("29")). - User-defined — you can define your own conversion operators for custom types.
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:
- User input is always a string.
- Files and network streams give bytes.
- Calculations may require decimal for money, double for scientific values.
- APIs return JSON with mixed types.
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:
- Move data from one representation to another.
- Control whether a conversion is safe (implicit) or risky (explicit).
- Handle invalid input gracefully with
TryParse. - Define custom conversions for your own domain types (e.g., Money, Temperature).
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#:
string, int, double, object, custom types...
- Implicit (compiler auto)
- Explicit cast
(type) Parse/TryParseConverthelper- User-defined operators
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
int.TryParse— safely converts string to int without exceptions.quantity * unitPrice—intis implicitly converted todoubleso the multiplication works.(int)average— explicit cast truncates the decimal part.total:C— formats the double as currency using current culture.
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
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?
- Compiler determines if a conversion operator exists for the source/target pair.
- If implicit, it inserts the conversion automatically.
- If explicit, it requires a cast syntax.
- If no conversion exists, compilation fails.
- For built-in types, the CLR executes the corresponding CPU-level conversion instruction (e.g.,
conv.i4forint). - For custom types, the user-defined operator method is invoked.
- For
Parse/TryParse, the .NET library code parses characters, applies culture, and constructs the target value.
- Boxing: converting a value type to
objector an interface it implements. Allocates on heap. - Unboxing: extracting the value type back from
object. Can throwInvalidCastExceptionif types don't match.
- By default, arithmetic conversions are unchecked — overflow wraps silently.
- In a
checkedblock or withcheckedexpression, overflow throwsOverflowException. - You can set project-level
CheckForOverflowUnderflow.
- Generic math interfaces like
INumber<TSelf>define parsing and conversion methods consistently across numeric types. IParsable<TSelf>andISpanParsable<TSelf>are implemented by many types in .NET 7+.- In .NET 10 and C# 14, the fundamental conversion model remains the same, but more types implement these interfaces for generic programming.
Common Confusion
1. Implicit vs Explicit
Many beginners think all conversions are the same. The difference is:
- Implicit: compiler does it automatically; no syntax required; safe (widening).
- Explicit: developer must write a cast; may lose data; risky (narrowing).
2. Convert vs Parse vs TryParse
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:
- You are assigning a smaller integer type to a larger one (
int→long). - You are mixing numeric types in arithmetic and no data loss is expected (
int→double). - You have defined a custom implicit operator for a type that is a safe superset.
Use explicit cast when:
- You are converting from a larger numeric type to a smaller one (
double→int). - You are converting between reference types in an inheritance hierarchy (downcasting).
- You accept the risk of data loss or overflow.
Use Parse / TryParse when:
- Converting strings to numbers, dates, enums, etc.
- Parsing JSON, CSV, or user input.
- Always prefer
TryParsefor external input to avoid exceptions.
Use Convert class when:
- The source type is
objector may benull. - You need a quick conversion between primitive types with built-in null handling.
- You are working with database values (
DBNull).
When conversion might be unnecessary:
- If you can keep the same type throughout, you avoid unnecessary allocations and potential errors.
- For high-performance loops, avoid boxing/unboxing by using generic types or spans.
Mental Model
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
- Type conversion is necessary because data comes in many types, but operations require specific ones.
- Implicit conversions happen automatically when no data loss is possible.
- Explicit conversions (casts) require a cast and can lose data or overflow.
Parse/TryParseconvert strings to other types; preferTryParsefor untrusted input.Convertis a helper for object/null and primitive conversions.- Boxing/unboxing are special conversions between value types and
object. - Culture matters — always specify
InvariantCulturefor data interchange. - User-defined operators let you design safe conversions for your own types.
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.