C# · Beginner
Variables and Types in C#
In short: A C# variable is a named storage location declared with a type, such as int count = 3;, and it can only hold values of that type. Whole numbers go in int or long, numbers with a fraction in double (fast, binary) or decimal (exact, for money), true or false in bool, one character in char and text in string. var asks the compiler to infer the type from the initial value; the variable is still.
Why every variable has a type
C# is statically typed: the type of every variable is fixed when it is declared and checked by the compiler. int count = 3; reserves space for a 32-bit whole number named count; assigning the text "three" to it later is a compile error, not a surprise at run time. That check is the main reason C# programs fail early and loudly rather than late and quietly, and it is what lets the editor offer the right completions after every dot.
The numeric types differ in what they can hold. int covers whole numbers from about minus 2.1 billion to plus 2.1 billion; long reaches about 9.2 quintillion either way. double stores numbers with a fractional part as 64-bit binary floating point, which is fast and fine for measurements but cannot represent 0.1 exactly, so 0.1 + 0.2 is not quite 0.3. decimal stores 28 to 29 significant digits in base ten, so sums of prices come out exact; its literals carry an m suffix (4.50m) and it is the type for money. float is a 32-bit floating-point type with an f suffix, used mostly in graphics code.
Beyond numbers, bool holds true or false and is the only type a condition accepts; char holds one character in single quotes, 'B'; string holds text in double quotes and is immutable, so every operation on a string produces a new one. Printing a bool gives True or False with a capital letter, because that is what its ToString() returns.
Arithmetic follows the operands. When both sides of / are integers the result is an integer and the remainder is discarded: 12 / 7 is 1, and % gives the remainder 5. Make one side a floating-point value, for example by casting, (double)slices / loaves, to get 1.714.... Conversions that cannot lose information happen automatically (int to long or double); conversions that can, such as double to int, need an explicit cast, (int)3.99, which truncates toward zero to 3. Use Math.Round first when you want the nearest whole number.
Input arrives as text, so it must be parsed. int.Parse(text), double.Parse(text) and decimal.Parse(text) convert, and throw a FormatException if the text is not a valid number. int.TryParse(text, out int value) returns false instead of throwing and leaves value at 0, which is the right tool when the input may be malformed. Going the other way, every value has a ToString(), and string interpolation calls it for you; a format specifier after a colon controls the shape, so {bill:F2} always shows two decimal places.
var lets you omit the type when the initial value makes it obvious: var total = count * 2; is an int, and the compiler treats it exactly as if you had written int. It must be initialised on the same line and cannot change type later; it is a typing shortcut, not dynamic typing. const marks a value that never changes, such as const int MaxSeats = 48;, and the compiler rejects any assignment to it. By convention local variables and parameters are camelCase, while types, methods and properties are PascalCase.
Syntax
type name = value; // declare and initialise
int count = 3; // whole number
long population = 8_100_000_000; // larger whole number; _ separates digits (C# 7)
double ratio = 0.75; // binary floating point
decimal price = 4.50m; // exact decimal, m suffix
bool inStock = true;
char grade = 'B';
string label = "rye";
var total = count * 2; // inferred as int; must be initialised
const int MaxSeats = 48; // cannot be reassigned
int n = int.Parse(text); // text -> int, throws on bad input
bool ok = int.TryParse(text, out int m); // false instead of throwing
double d = n; // implicit widening conversion
int back = (int)3.99; // explicit cast: truncates to 3A local variable must be assigned before it is read; the compiler reports an error for a variable that might still be unassigned.
Whole numbers, fractions and two kinds of division
Integer arithmetic, the double versus decimal difference, and how each type prints.
int loaves = 7;
int slices = 12;
Console.WriteLine(loaves * slices);
Console.WriteLine(slices / loaves);
Console.WriteLine(slices % loaves);
Console.WriteLine((double)slices / loaves);
double bag = 0.1 + 0.2;
decimal till = 0.1m + 0.2m;
Console.WriteLine(bag);
Console.WriteLine(till);
Console.WriteLine(bag == 0.3);
long population = 8_100_000_000;
char grade = 'B';
bool open = slices > loaves;
Console.WriteLine(population / 1_000_000);
Console.WriteLine(grade);
Console.WriteLine(open);Output
84 1 5 1.7142857142857142 0.30000000000000004 0.3 False 8100 B True
slices / loaves divides two ints, so the result is the whole-number part 1, and % supplies the remainder 5; casting one operand to double first gives the full quotient. 0.1 + 0.2 in double prints as 0.30000000000000004 because neither 0.1 nor 0.2 has an exact binary form, and the comparison with 0.3 is therefore False; the same sum in decimal is exactly 0.3. long holds the 8.1 billion that would overflow an int, and the bool prints as True.
Turning input text into numbers
Two lines of text become an int and a decimal, and the results are formatted in several ways.
string peopleText = Console.ReadLine()!;
string priceText = Console.ReadLine()!;
int people = int.Parse(peopleText);
decimal price = decimal.Parse(priceText);
decimal bill = people * price;
Console.WriteLine($"{people} people at {price} each = {bill}");
Console.WriteLine($"Formatted: {bill:F2}");
var share = bill / 2;
Console.WriteLine(share);
int rounded = (int)Math.Round(bill);
Console.WriteLine(rounded);
bool ok = int.TryParse("3h", out int parsed);
Console.WriteLine(ok);
Console.WriteLine(parsed);
Console.WriteLine(people + " " + price);Input given to the program: 3 ↵ 2.4
Output
3 people at 2.4 each = 7.2 Formatted: 7.20 3.6 7 False 0 3 2.4
int.Parse and decimal.Parse turn the text into values the program can multiply. {bill:F2} pads 7.2 to 7.20, which is what a receipt needs. var share is inferred as decimal because bill / 2 is a decimal. Math.Round on a decimal returns a decimal, so an explicit (int) cast is needed to store it in an int. TryParse reports False for 3h and leaves parsed at 0 instead of crashing. On the last line + joins an int, a string and a decimal into one string; each number is converted by its own ToString().
Common mistakes
Expecting 7 / 2 to be 3.5
Why it goes wrong: Both operands are int, so the division is integer division and the result is 3. The fraction is discarded before anything is printed.
Fix: Make at least one operand a double or decimal:
7 / 2.0,(double)a / b, or declare the variables as double.C# · fixint a = 7; int b = 2; Console.WriteLine((double)a / b); // 3.5Storing money in a double
Why it goes wrong: Binary floating point cannot represent most decimal fractions exactly, so a column of prices added in double can differ from the printed total by a cent, and
0.1 + 0.2 == 0.3is false.Fix: Use
decimalwith themsuffix for currency and anything that must add up exactly.C# · fixdecimal subtotal = 19.99m + 0.01m; Console.WriteLine(subtotal == 20m); // TrueAssigning a double to an int without a cast
Why it goes wrong:
int whole = 3.7;fails witherror CS0266: Cannot implicitly convert type 'double' to 'int', because the conversion could lose information and C# refuses to do that silently.Fix: Cast explicitly to truncate, or round first:
(int)3.7is 3 and(int)Math.Round(3.7)is 4.Declaring var without a value
Why it goes wrong:
var x;giveserror CS0818: Implicitly-typed variables must be initialized; the compiler needs the initial value to work out the type.Fix: Give the value on the same line, or write the type explicitly (
int x;) and assign later.
Built-in types at a glance
| Type | Holds | Example literal | Notes |
|---|---|---|---|
| int | whole numbers, 32-bit | 42 | about plus or minus 2.1 billion; the default choice for counting |
| long | whole numbers, 64-bit | 8_100_000_000L | about plus or minus 9.2 quintillion; the L suffix marks a long literal |
| double | fractional, binary | 0.75 | fast; about 15 to 17 significant digits; not exact for 0.1 |
| decimal | fractional, base ten | 4.50m | exact for money; 28 to 29 significant digits; slower |
| bool | true or false | true | the only type an if condition accepts |
| char | one character | 'B' | single quotes |
| string | text | "rye" | double quotes; immutable |
Where you use this
A receipt program is a good place to see every type doing its job. Quantities are whole numbers, so they are int; unit prices are money, so they are decimal; the customer's name is a string; whether they hold a loyalty card is a bool. Reading each of those from input means parsing text into the right type, and the type chosen up front decides what the arithmetic later will do: an int quantity times a decimal price gives an exact decimal total, and {total:F2} prints it in the shape a receipt needs. When the input comes from a person rather than a fixed file, TryParse lets the program complain politely instead of crashing.
int quantity = int.Parse(Console.ReadLine()!);
decimal unitPrice = decimal.Parse(Console.ReadLine()!);
bool loyalty = Console.ReadLine() == "yes";
decimal total = quantity * unitPrice;
if (loyalty)
{
total = total * 0.9m;
}
Console.WriteLine($"Total: {total:F2}");Key points
- Every variable has one fixed type, checked at compile time.
intandlongfor whole numbers;doublefor measurements;decimal(with themsuffix) for money.- Integer division discards the remainder and
%returns it; cast one operand to get a fraction. - Widening conversions are automatic; narrowing ones need an explicit cast.
int.Parseconverts text and throws on bad input;int.TryParsereturns false instead.varinfers the type from the initial value and is still statically typed.- Format values inside interpolated strings with specifiers such as
{x:F2}.
Try it yourself
The program reads a number of tickets and a price per ticket, but it stores the price as an int, so a price such as 12.50 cannot be parsed. Change the type of the price variable so that the program prints the total with two decimal places.
int tickets = int.Parse(Console.ReadLine()!);
int price = int.Parse(Console.ReadLine()!);
Console.WriteLine($"{tickets * price:F2}");
3 ↵ 12.5037.50int tickets = int.Parse(Console.ReadLine()!);
decimal price = decimal.Parse(Console.ReadLine()!);
Console.WriteLine($"{tickets * price:F2}");
Practise this
Related lessons
- C# Syntax and Your First ProgramHow a C# program is put together on .NET 8: top-level statements, semicolons and braces, comments, Console.WriteLine for output and Console.ReadLine for input.8 min
- Conditions and Loops in C#How C# chooses and repeats: if, else if and else, switch statements and expressions, for, while, do-while and foreach loops, and break and continue, with.11 min
Frequently asked questions
What is the difference between double and decimal in C#?
double is 64-bit binary floating point: fast, with a huge range and about 15 to 17 significant digits, but unable to represent most decimal fractions exactly, which is why 0.1 + 0.2 prints 0.30000000000000004. decimal is a 128-bit base-ten type with 28 to 29 significant digits that stores 0.1 exactly, at the cost of speed and range. Use double for physical measurements and scientific work, and decimal for money and anything that must add up to the cent.
Is var in C# the same as dynamic typing?
No. var only asks the compiler to work out the type from the initialiser; after that the variable is exactly as fixed as if you had written the type. var n = 5; makes an int, and assigning text to n later is a compile error. The dynamic keyword is the one that defers type checks to run time, and it is rarely needed.
Why does 7 / 2 give 3 in C#?
Because both operands are integers, and / on two integers performs integer division, discarding the remainder. Convert one operand to a floating-point type first, 7 / 2.0 or (double)a / b, to get 3.5. The % operator gives the discarded remainder: 7 % 2 is 1.
How this page was checked. Every program on it was run with .NET SDK 8.0 at build time by the publishing checks, and the output shown is what it printed. Running C# inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with .NET SDK 8.0 locally.