Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

Convert

Convert.ToInt(<value>) · Convert.ToInt64(<value>) · Convert.ToDecimal(<value>) · Convert.ToDouble(<value>) · Convert.ToString(<value>) · Convert.ToBool(<value>) · Convert.ToInt32(<text>, <base>)

Explicit conversion between types — number to text, text to number, a Guid to its text form. Osy# will not convert silently where information could be lost, so you say it outright.

stable7 examples compiled by CIfunctionstdlibtypes

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>)   // → bool

Convert.ToInt32(<string> s, <int> fromBase)   // → int — base 2, 8, 10 or 16; THROWS on text it cannot read

Description#

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)amounta 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") throws FormatException. This answers 0 — no error, no null, a number that is wrong. So osy lint refuses Convert.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 here

A 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)).

Reading hex, binary or octal#

Convert.ToInt32(s, 16) is C#'s base overload, and it behaves exactly as C#'s does: bases 2, 8, 10 and 16 only, an optional 0x in base 16, and a value above int.MaxValue read as two's complement in bases 2/8/16 — so "FFFFFFFF" is -1. Unlike the one-argument parse above it throws on text it cannot read (a digit outside the base, a sign outside base 10, a space), as C# does: a hex colour or checksum with a bad digit is a fault, not a 0. A literal base outside the four is a compile error. It runs in the browser too, with the same answers.

int Channel(string hex, int at) => Convert.ToInt32(hex.Substring(at, 2), 16);
[Test]
void Base_parse_answers() {
  Assert.Equal(255, Channel("ff8000", 0));
  Assert.Equal(128, Channel("ff8000", 2));
  Assert.Equal(-1, Convert.ToInt32("FFFFFFFF", 16));
  Assert.Equal(5, Convert.ToInt32("101", 2));
  Assert.Throws(() => Convert.ToInt32("fg", 16));
}

A second argument that is TEXT is a different overload — the culture the number is written in, Convert.ToInt32(s, "sv-SE") — see Culture formatting — ToString(format, culture).

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.

Can out stand inside an expression?#

Yes — int.TryParse(s, out var n) ? n : 0 works wherever an expression does: a local's initializer, a return, an argument, an object initializer's value, an interpolation hole. d.TryGetValue(k, out v) fills a variable that already exists (with default(V) on a miss, as C# does), and out _ discards the value. The one position that keeps the refusal is a LAMBDA body, whose parse runs once per element.

Definite assignment works through || and && as in C#: after if (!int.TryParse(a, out var y) || !int.TryParse(b, out var m)) { return null; } both y and m hold numbers, and inside if (int.TryParse(a, out var y) && int.TryParse(b, out var m)) { … } so do both.

string Quantity(string text) => $"{(int.TryParse(text, out var n) ? n : 0)} items";
int Stock(Dictionary<string, int> d, string sku) { int qty = -1; return d.TryGetValue(sku, out qty) ? qty : 0; }
bool IsNumber(string text) => int.TryParse(text, out _);

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.

In a query#

A conversion answers the same inside a query the database runs — over the app's own tables, and on a data source that takes the query — as it does in a function:

Convert.ToInt(o.Total) where the total is null0 — a null converts as it does in memory; Convert.ToString of one is "", Convert.ToBool false
Convert.ToInt(o.Ratio) holding NaN, ±Infinity or 1e20the read fails, as C#'s Convert.ToInt32 does: a double with no whole-number value has no int
Convert.ToBool(o.Ratio) holding NaNtrue — NaN is not zero
Convert.ToString(o.Ratio)a double as .NET writes it: 1E+20, 1000000000000000, 1E-05

A data source runs a conversion only where it can mean exactly that. A double written as text, and text read as a double, are refused on a data source's entity — convert them after .ToList(). A date written as text is sent in the culture's own pattern; see Culture formatting — ToString(format, culture).

See also#

Related

(int)x — casts

A C-style cast converts between the numeric types. It truncates toward zero, and it is checked — a value the target…

Numeric types & literal suffixes

The platform numeric types are int, long, decimal, and double. Literals follow C# exactly, suffixes (L, m, d) included:…

Typed locals

Locals can declare an explicit type instead of var; the declared type pins the binding, and every later assignment to…

String interpolation & format specifiers

Build a string from literal text and embedded expressions with $"…{expr}…". A hole may carry a .NET format specifier…

long

A 64-bit whole number, exact to its full range — ids, sequence numbers, row versions, byte offsets. It holds the same…