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