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.
Type conversion means taking a value of one type and producing a corresponding value of another type.
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:
(targetType) or operators; may lose data or throw exceptions.int.Parse("29")).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.
Type conversion provides the bridge between different type systems. It allows you to:
TryParse.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.
Here's a visual mental model of how conversions flow in C#:
string, int, double, object, custom types...
(type)Parse / TryParseConvert helperLet's trace what happens when you perform different kinds of conversions.
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.
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.
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.
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.
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.
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 — int is implicitly converted to double so the multiplication works.(int)average — explicit cast truncates the decimal part.total:C — formats the double as currency using current culture.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.
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.
What happens inside the CLR and compiler when you convert types?
conv.i4 for int).Parse/TryParse, the .NET library code parses characters, applies culture, and constructs the target value.object or an interface it implements. Allocates on heap.object. Can throw InvalidCastException if types don't match.checked block or with checked expression, overflow throws OverflowException.CheckForOverflowUnderflow.INumber<TSelf> define parsing and conversion methods consistently across numeric types.IParsable<TSelf> and ISpanParsable<TSelf> are implemented by many types in .NET 7+.Many beginners think all conversions are the same. The difference is:
Convert vs Parse vs TryParseasFor 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
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
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.
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.
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).
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; }.
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;.
Convert where TryParse is betterConvert.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).
int → long).int → double).double → int).Parse / TryParse when:TryParse for external input to avoid exceptions.Convert class when:object or may be null.DBNull).TryParse for user inputchecked when overflow mattersParse/TryParse convert strings to other types; prefer TryParse for untrusted input.Convert is a helper for object/null and primitive conversions.object.InvariantCulture for data interchange.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#?
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?
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);
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?
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?
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.