Summary#
Locals can declare an explicit type instead of var — int x = 5;, Order o = …;,
List<int> xs = …;. The declared type pins the binding, exactly C#: literal initializers apply the
C# constant conversion, non-literal initializers must be implicitly assignable, and every typed local
must be initialized at its declaration. The same rule governs every later x = … — the declaration
and the assignment on the next line are checked by one function, so they cannot disagree.
Signature#
<Type> <name> = <initializer>;
<name> = <value>; // …and every later assignment, by the same rule
// Type: a scalar keyword, entity (incl. namespaced), Type?, Type[], Type[][],
// List<T>/HashSet<T>/Dictionary<K,V>Description#
- A typed local must be initialized at its declaration —
int x;is refused (there is no definite-assignment analysis; initialize where you declare). - Literal initializers use the C# constant conversion: the literal is re-kinded when widening
(
int → long/decimal/double,decimal → double) — sodouble h = 2.5;compiles even though a bare2.5is decimal (Numeric types & literal suffixes). A non-representable constant refuses:int x = 2.5m;→cannot implicitly convert 'decimal' to 'int'. - Non-literal initializers widen by numeric rank (
int → long → decimal/double) but never across decimal↔double in either direction — C# has no implicit conversion between them (double x = someDecimal;is a pointed error). nullneeds a nullable declared type —int? x = null;, notint x = null;.- The pinned type drives downstream resolution — an entity-typed local navigates members
(
Order o = Order.First(); o.Total). - Decompile normalizes to
varwith the re-kinded literal (decimal d = 5;→var d = 5m;) — semantically identical; the same normalization const-inlining uses.
Assigning to it afterwards#
Every assignment is checked against the slot, by the rule above. int x = 1; x = "hello"; is refused, and the
message names the variable, what it holds, what you tried to store and the line the type was decided on. This holds
wherever the value lands and however it is spelled:
| The slot | Example |
|---|---|
a typed local, or one inferred by var | int x = 1; x = "hello"; |
| a parameter | void F(int p) { p = "hello"; } |
a for / foreach / catch variable | foreach (var i in xs) { i = "hello"; } |
| a compound assignment | x += "hello"; |
| a chain | a = b = "hello"; |
a component field, with or without this. | counter = "hello"; inside an action or a render lambda |
| a dictionary, list or array element — and the key | d[42] = 1; on a Dictionary<string, int> |
a class indexer's set | bag["a"] = "hello"; on int this[string key] |
The conversions it ACCEPTS are the same ones a declaration, an argument and a return accept: the C# constant
conversion for a numeric literal (decimal d = 0; d = 1; stores a decimal), numeric widening by rank, and the
implicit upcast to a base type. decimal ↔ double is refused in both directions, as in C#.
Declaring several names with one type#
int x = 1, y = 2; declares both names with the one type, in the enclosing scope, exactly as C# does. A name may
be left without a value (int a, b;) and assigned before it is read. var takes a single name, as in C# (CS0819).
A name that is a keyword is written verbatim — int @class = 3; — and the @ is spelling only: the name is class.
int Area() { int w = 3, h = 4; int a, b; a = w; b = h; return a * b; } // 12What is a type's default value?#
default(int) is 0, default(bool) is false, and every other type's default is null, as in C#. The bare
default takes the type of the place it is written — int n = default;, return default; — and where nothing gives
it one (var v = default;) the refusal says so and asks for default(T). sizeof(int) is C#'s constant size of a
built-in value type: bool/byte 1, char 2, int 4, long/double 8, decimal 16.
int Zeroes() { int a = default; long b = default(long); return a + (int)b + sizeof(int); } // 4
string Nothing() { string? s = default; return s ?? "none"; } // "none"Examples#
entity Order { decimal Total; }
decimal Examples() {
int x = 5; // pins int
long big = 5; // constant conversion: the int constant becomes long
decimal d = 5; // → 5m
double h = 2.5; // works — the constant converts (a bare 2.5 is decimal)
int? maybe = null; // nullable declared type accepts null
Order? o = Order.FirstOrDefault(); // entity-typed local — `?`, because …OrDefault() may answer null
if (o != null) { return o.Total + d; }
return d + x + big + maybe ?? 0;
}decimal Later() {
int x = 1;
x = 2; // fine — same type
decimal d = 0;
d = 1; // fine — the int constant becomes a decimal, as at a declaration
long big = 0;
big = x; // fine — int widens to long
// x = "hello"; // refused: cannot assign 'string' to `x`, which holds 'int'
// d = 1.5; ⟵ fine too; but `double h = 0; h = d;` is refused — no decimal↔double conversion
return d + big;
}⚠ The ? on o is the example, not a typo. A …OrDefault() read answers null when nothing matches, so a
non-nullable Order o would be holding a null the moment the table is empty — and every later read of it would be an
unguarded one. The compiler refuses that declaration and names both honest choices: Order? o if absent is a case you
handle, or First() if it is not, which fails loudly at the read instead of handing you a null that surfaces
somewhere else. This page carried the non-nullable form until 2026-08-27, when the check that catches it landed.
See also#
- Numeric types & literal suffixes — the literal kinds the constant conversion re-kinds between
- const —
const Type name = …;(the compile-time-constant sibling)