Type Conversion
Learn implicit and explicit type conversion, casting, and the Convert and Parse methods.
Implicit Conversion
C# automatically converts between compatible types when no data would be lost — for example, converting an `int` to a `double`, since every whole number is also perfectly representable as a floating-point number. This is called a "widening" conversion, because you're moving to a type that can hold everything the original could, and more.
int wholeNumber = 42;double decimalNumber = wholeNumber; // implicit, safe — no data can be lostConsole.WriteLine(decimalNumber);
long bigNumber = wholeNumber; // int → long is also implicitfloat f = wholeNumber; // int → float is also implicitClick Run to see what this code prints.
| From | To | Always Implicit? |
|---|---|---|
| int | long, float, double, decimal | Yes |
| float | double | Yes |
| char | int | Yes (a char's underlying numeric code) |
| long | int | No — requires an explicit cast, covered next |
Explicit Conversion (Casting)
Converting from a larger/more-precise type to a smaller/less-precise one risks losing data, so C# requires an explicit cast — parentheses around the target type — to make that risk visible directly in your code, rather than letting it happen silently.
double price = 19.99;int roundedDown = (int)price; // explicit cast — truncates, doesn't round!Console.WriteLine(roundedDown);
long bigNumber = 5_000_000_000;int truncated = (int)bigNumber; // may lose data if it doesn't fit in an intClick Run to see what this code prints.
(int)19.99 produces 19, not 20 — casting simply chops off everything after the decimal point. If you actually want rounding, use Math.Round() first, then cast the result: (int)Math.Round(19.99).
What Happens When Data Is Lost
When a value genuinely doesn't fit in the smaller target type, the result is not an error by default — it silently wraps around to an unexpected value, which can be a genuinely dangerous source of subtle bugs in production code.
long tooLarge = 5_000_000_000; // larger than int can holdint result = (int)tooLarge;Console.WriteLine(result); // 705032704 — NOT 5 billion, and NOT an error!The Convert Class
The `Convert` class handles conversions between otherwise unrelated types that a simple cast can't bridge, like turning a string into a number — casting only works between numerically related types, not between a string and an int.
string input = "25";int age = Convert.ToInt32(input);Console.WriteLine(age + 5);
// Convert also handles bool, DateTime, and morebool flag = Convert.ToBoolean("true");string backToString = Convert.ToString(42);Click Run to see what this code prints.
Convert.ToInt32("not a number") throws a FormatException — Convert is more flexible than a plain cast, but it is not automatically "safe" for untrusted input; that's exactly what Parse/TryParse (covered next) are for.
Parse & TryParse
`Parse` (called on the target type itself, like int.Parse) throws an exception if the string isn't a validly formatted value of that type. `TryParse` is the safer alternative — it returns `true`/`false` to indicate success instead of throwing, storing the actual result in an `out` parameter. This distinction matters enormously when handling real, untrustworthy user input.
string userInput = "abc";
if (int.TryParse(userInput, out int result)){ Console.WriteLine($"Parsed: {result}");}else{ Console.WriteLine("Invalid number.");}Click Run to see what this code prints.
Any time you're converting input that might not be a valid number (like form data, console input, or an external API response), use TryParse instead of Parse to avoid crashing your program on unexpected input — this is one of the single most impactful habits for writing robust, production-quality C# code.
A full comparison of when to reach for each conversion mechanism:
| Mechanism | From → To | On Invalid Input |
|---|---|---|
| Implicit conversion | A compatible, wider type | Not applicable — the compiler guarantees safety |
| Explicit cast (T) | A related, narrower numeric type | Silently produces wrong data (no exception) |
| Convert.ToX() | Unrelated types (string number, etc.) | Throws FormatException |
| X.Parse() | string → a specific type | Throws FormatException |
| X.TryParse() | string → a specific type | Returns false, no exception |
Boxing and Unboxing
Boxing is the process of wrapping a value type (like an int) inside an object reference, so it can be treated as a reference type — necessary when passing a value type somewhere that expects the general-purpose object type. Unboxing reverses this, extracting the original value type back out. Both involve real, measurable performance overhead compared to working with the value type directly, since boxing allocates new memory on the heap.
int number = 42;object boxed = number; // boxing — number is wrapped in an object on the heapint unboxed = (int)boxed; // unboxing — explicit cast requiredConsole.WriteLine(unboxed); // 42Generics (covered in a dedicated lesson later in this course) were introduced in C# 2.0 largely to eliminate exactly this kind of unnecessary boxing — older, pre-generics collection types stored everything as object, forcing a box/unbox round trip for every value type stored or retrieved.
Checked and Unchecked Contexts
By default, C# arithmetic overflow (like adding 1 to int.MaxValue) wraps around silently rather than throwing — an "unchecked" context, which is the implicit default for performance reasons. Wrapping code in an explicit checked block makes C# throw an OverflowException instead, trading a small amount of performance for safety.
int max = int.MaxValue;
int wrapped = max + 1; // unchecked (default) — silently wraps to a very negative numberConsole.WriteLine(wrapped); // -2147483648
checked{ int willThrow = max + 1; // throws System.OverflowException}Common Beginner Mistakes
(int)19.99 truncates to 19, it does not round to 20 — use Math.Round() explicitly first if you want mathematical rounding.
A single unexpected character in a user-typed string crashes int.Parse() with an unhandled exception — TryParse avoids this entirely with a simple boolean check.
Casting a long that's too large for int doesn't throw an error by default — it produces a wrong, wrapped-around value that can be very hard to trace back to its source.
FAQs
It overflows silently in the default unchecked context, producing an unexpected wrapped value — always validate ranges before casting untrusted or externally-sourced data, or wrap the cast in a checked block.
Yes — double.TryParse, bool.TryParse, DateTime.TryParse, decimal.TryParse, and more all follow the identical pattern.
Because sometimes truncation or wrapping is genuinely the intended behavior (like deliberately taking only the whole-number part of a price) — the cast syntax simply makes clear, at the point you write the code, that you are aware data could be affected.
Rarely with modern C# and generics — it mostly matters in performance-critical code, or when working with older non-generic APIs (like the ArrayList class) that predate generics.
Key Takeaways
- Implicit conversion is automatic and safe (no data loss) — only allowed when the compiler can guarantee correctness.
- Explicit casting is required when data might be lost, and truncates (doesn't round) when converting floating-point to integer.
- Convert.ToX() bridges unrelated types like string and int, but still throws on invalid input.
- Prefer TryParse over Parse whenever converting untrusted or user-provided input.
- By default, arithmetic overflow wraps silently (unchecked); wrap in a checked block to throw an exception instead.
Summary
Type conversion in C# ranges from fully automatic and safe (implicit) to explicit and risky (casting) to defensively safe (TryParse) — choosing the right mechanism for the right situation is a hallmark of careful, production-quality code. Next, you'll learn the operators C# uses to actually compute and compare these values.