Summary#
Convert.* converts between types explicitly. Osy# does not convert silently where information could be lost — a
decimal does not quietly become an int, dropping the pence — so where a conversion is what you want, you write it.
Signature#
Convert.ToInt(<value>) // → int (32-bit; truncates toward zero)
Convert.ToInt64(<value>) // → long (64-bit; truncates toward zero)
Convert.ToDecimal(<value>) // → decimal
Convert.ToDouble(<value>) // → double (float64)
Convert.ToString(<value>) // → string (numbers, Guids, bools …)
Convert.ToBool(<value>) // → bool
// C#'s own spellings work too — `Convert.ToInt32` IS `Convert.ToInt`, and `Convert.ToBoolean` IS
// `Convert.ToBool`. The same arms, so they behave identically; write whichever you reach for.
Convert.ToInt32(<value>) // → int
Convert.ToBoolean(<value>) // → boolDescription#
Convert.* or a cast?#
Both narrow, and they are not interchangeable. Between two NUMBERS, reach for the cast — (int)x — which is the
C# spelling and reads at a glance; see (int)x — casts. Reach for Convert.* when the input might not be a number
at all, or when the output is text:
(int)amount | a numeric conversion. Truncates toward zero, and fails if the value does not fit |
Convert.ToInt(s) | a parse. Reads an integer out of text, and answers 0 for text it cannot read |
Convert.ToString(v) | renders a value as text |
The difference in the failure is the reason they are separate: a cast that cannot produce a value stops, while a
parse of user-supplied text answers 0 and carries on — which is right for a form field and wrong for arithmetic.
⚠ This is where Osy# and C# differ, and it is the difference most likely to cost you. C#'s
Convert.ToInt32("abc")throwsFormatException. This answers 0 — no error, no null, a number that is wrong. Soosy lintrefusesConvert.To*over text at MUST tier (correctness-parse-that-answers-zero): say which you mean, and say it where the reader can see it.if (decimal.TryParse(entered, out var cap)) { … } else { … } // the C# spelling, and it compiles hereA numeric conversion —
Convert.ToInt(Math.Floor(x))— is untouched and behaves exactly as C# does. The lenient parse is still here and still useful; it just has to be asked for out loud.
Narrowing is explicit#
Widening happens on its own — an int is usable where a decimal is expected, because nothing is lost. Going the
other way loses the fraction, so you must say so:
int WholeUnits(decimal amount) {
return Convert.ToInt(Math.Floor(amount)); // decide the rounding, THEN narrow
}Note the Math.Floor first. Convert.ToInt truncates toward zero, which is a rounding decision — and a rounding
decision you make by accident is a rounding bug. Choose it deliberately: Math.Floor, Math.Ceiling, or
Math.Round.
Convert.ToInt64 is the same narrowing to a 64-bit long instead of a 32-bit int — reach for it when the
value can exceed ±2.1 billion (an id, a byte count, a running total of small amounts). It truncates toward zero
exactly as Convert.ToInt does, so the same "round on purpose first" rule applies. See long for what a
long is and why its range matters.
long TotalCents(decimal amount) {
return Convert.ToInt64(Math.Round(amount * 100m)); // money as whole cents, 64-bit headroom
}How do I get a double for non-money maths?#
Convert.ToDouble is the float64 front door. Reach for it when the value in hand is a decimal and the arithmetic
downstream is not money — geometry, physics, a per-frame camera. Math.Round, Math.Floor, Math.Ceiling and
Math.Truncate all answer a decimal when given one, so this is the conversion back:
double SnappedTo(decimal value, decimal step) {
return Convert.ToDouble(Math.Round(value / step) * step);
}It coerces like its siblings: text is parsed (0 for text it cannot read), null is 0, true is 1. Two
things are true of a double that are not true of an int or a decimal, and both show up here:
Convert.ToDouble("NaN") · ("Infinity") · ("-Infinity") | those are values a double HAS, so the text reads (any casing, optional sign) |
Convert.ToDouble("1e400") | +Infinity — an out-of-range magnitude is not a parse failure for this type (and "1e-400" is 0) |
⚠ A double is not money. It cannot hold 0.1 exactly, so converting a price to one and back loses the pence
silently — see Numeric types & literal suffixes for which type a value should have been in the first place.
What counts as a number#
Every Convert.To* reads text the same way, so a string that parses for one parses for all of them. The accepted
shape is:
[white space] [+ or -] digits[,digits…][.digits] [e or E [+ or -] digits] [white space]"1,000" · "12,34" · "100," | group separators are accepted, and their PLACEMENT is not checked |
" 42 " · "\t42\n" | a space, tab, newline, vertical tab, form feed or carriage return may pad it |
"1e3" · "1.5e+2" · ".5" · "1." | an exponent, a bare fraction and an empty fraction are all fine |
",100" · "1.0,5" | ✗ a leading separator, or one in the fraction |
"(5)" · "5-" · "¤5" | ✗ accounting parentheses, a trailing sign, a currency symbol |
" 42" (non-breaking space) | ✗ a non-breaking space is not white space to a number |
"0x1F" · "nope" · "" | ✗ — and the answer is 0, never an error and never NaN |
The exact same grammar runs in the browser and inside a query the database executes, so a conversion answers the same
thing wherever the expression happens to run. Convert.ToDouble reads three more forms, because a double has values
the other types do not: "NaN", "Infinity" and "-Infinity", in any casing.
An integer conversion additionally refuses a non-zero fraction — Convert.ToInt("42.9") is 0, while
Convert.ToInt("42.0") is 42. If you want the number rounded, round it: Convert.ToInt(Math.Floor(x)).
How do I turn a number or a Guid into text?#
string Describe(int count, Guid id) {
return "count=" + Convert.ToString(count) + " id=" + Convert.ToString(id);
}For building a sentence out of several values, string interpolation reads better —
$"count={count}" converts for you.
Money stays decimal#
Resist converting money to int or back. If you find yourself doing it to make arithmetic work, the accumulator is
probably an int that should have been seeded 0m — see var.
See also#
- (int)x — casts —
(int)x/(double)x, the cast: converting between the NUMERIC types, and failing rather than answering 0 - Numeric types & literal suffixes — the literal suffixes, and which type a bare
0is - String interpolation & format specifiers —
$"…{value}…", which converts for you - Typed locals — pinning a local's type at its declaration