Every application needs to store and manipulate data: a book's title, its number of pages, whether a copy is available or not. In C#, that data is stored in variables, and every variable has a type that determines what kind of values it can hold and what operations can be performed on them. In this lesson you'll learn to declare variables, to tell value types apart from reference types, to convert data from one type to another, to define constants, and to understand a variable's scope. We'll always use data from the BiblioTech catalog — title, ISBN, page count, availability — as the common thread running through every example.
Contents
- Declaring variables: explicit types and
var - Value types
- Reference types
- Type conversion
- Constants:
constandreadonly - Variable scope
- Default values and basic nullability
Declaring variables: explicit types and var
Declaring a variable in C# means telling the compiler three things: its type, its name, and (optionally, but recommended) an initial value. The general syntax is:
For example, to represent a BiblioTech book's title:
string bookTitle = "One Hundred Years of Solitude";
int pageCount = 471;
bool isAvailable = true;
Console.WriteLine(bookTitle);
Console.WriteLine(pageCount);
Console.WriteLine(isAvailable);The var keyword
C# also lets you declare a variable using the var keyword, letting the compiler infer the
type from the assigned value:
var bookTitle = "One Hundred Years of Solitude"; // the compiler infers 'string'
var pageCount = 471; // the compiler infers 'int'
var isAvailable = true; // the compiler infers 'bool'It's very important to understand that var does not turn C# into a dynamically typed
language: the type is still fixed at compile time, exactly as if you had written it explicitly;
the compiler simply infers it for you from the assigned value. That's why, once declared with
var, the variable remains "tied" to that type:
var pageCount = 471;
// pageCount = "four hundred"; // ERROR: can't assign a string to a variable inferred as int| Aspect | Explicit type (int x = 5;) |
var (var x = 5;) |
|---|---|---|
| Requires an initial value? | No | Yes, always (the compiler needs the value to infer the type) |
| Can the type change afterward? | No | No (once inferred, it's fixed) |
| When to use it | When the type adds clarity when reading the code | When the type is obvious from the value itself, or is long to write |
In this course we'll use both forms depending on context; neither is absolutely "better",
although many professional teams prefer var when the type is obvious from the assigned value
(as in our examples), and the explicit type when it aids clarity.
Value types
Value types are those where the variable directly holds the data. When you copy a value-type variable into another, the entire piece of data is copied; each variable is completely independent of the other. The most common value types in C# are the numeric types, the boolean, and the character type:
| Type | Represents | Example | Approximate size |
|---|---|---|---|
int |
Integer number | 471 |
32 bits |
long |
Large integer number | 9999999999L |
64 bits |
double |
Decimal (floating-point) number | 19.99 |
64 bits |
decimal |
High-precision decimal number | 19.99m |
128 bits |
bool |
True/false logical value | true, false |
1 bit (logical) |
char |
A single character | 'A' |
16 bits |
Example applied to BiblioTech:
int pageCount = 471;
double bookPrice = 24.90;
decimal exactPrice = 24.90m; // the 'm' suffix indicates it's a 'decimal'
bool isAvailable = true;
char authorInitial = 'G';
Console.WriteLine(pageCount);
Console.WriteLine(bookPrice);
Console.WriteLine(exactPrice);
Console.WriteLine(isAvailable);
Console.WriteLine(authorInitial);double or decimal?
Both represent numbers with decimals, but they're used differently:
doubleis faster and takes up less space, but can have small inaccuracies in certain operations (due to how it represents numbers internally in binary).decimalis more precise, designed specifically for calculations where accuracy matters a great deal, such as money (for example, a book's price or a late-return fine in BiblioTech). Its mandatory suffix is the lettermat the end of the literal number.
As a rule of thumb: for amounts of money, always use decimal; for other numeric calculations
involving decimals, double is usually enough.
Reference types
Unlike value types, reference types don't store the data directly in the variable, but
rather a reference (a kind of "address") pointing to where the data is actually stored. By
far the most used reference type is string:
The most important practical difference between value types and reference types shows up when copying variables:
// With a VALUE type (int): each variable is independent
int bookAPages = 471;
int bookBPages = bookAPages; // the number is copied
bookBPages = 500;
Console.WriteLine(bookAPages); // still shows 471: it was not affectedWith reference types, we'll see in Module 3 (when we work with objects like Book) that two
variables can point to the same data in memory, so that changing it through one variable
also affects what you see through the other. string is a special case of reference type that,
for everyday practical purposes, behaves a lot like a value type, thanks to its
immutability (which we'll study in detail in the next lesson): once a string is created, its
contents can't be changed; any operation that "seems" to change it actually creates a new
string.
| Value types | Reference types | |
|---|---|---|
| Examples | int, double, bool, char, decimal |
string, arrays, and (Module 3) classes we define ourselves |
| What the variable holds | The data directly | A reference to the data, stored elsewhere in memory |
| When a variable is copied | The whole piece of data is copied (independent) | The reference is copied (both variables can point to the same data) |
Don't worry if this distinction isn't completely clear yet: we'll revisit it with much richer examples when we work with our own classes in Module 3, where its implications become more visible.
Type conversion
Sometimes we need to convert data from one type to another: for example, a page count entered
as text (string) needs to be converted to int before we can do calculations with it. C#
offers several ways to do this.
Implicit conversion
Happens automatically when there's no risk of losing information, for example when going from a "smaller" type to a "larger" one:
int pageCount = 471;
double pageCountAsDouble = pageCount; // implicit conversion: int -> double, no data lossExplicit conversion (casting)
Needed when there can be a loss of information (for example, from double to int, where
the decimal part is lost). It's indicated by writing the target type in parentheses:
double priceWithDecimals = 24.90;
int roundedPrice = (int)priceWithDecimals; // explicit conversion: the decimal part is lost (becomes 24)Convert and Parse/TryParse
The most common conversion in practice is converting text (string) to a numeric type, for
example when reading a value the user entered at the console:
string pageCountText = "471";
// Option 1: using the Convert class
int pages1 = Convert.ToInt32(pageCountText);
// Option 2: using the type's own Parse method
int pages2 = int.Parse(pageCountText);
Console.WriteLine(pages1);
Console.WriteLine(pages2);Both options throw a runtime error if the text can't be converted (for example, if it
contains letters). To keep the program from stopping abruptly when the data is invalid, there's
a safer alternative: TryParse.
string userInput = "four hundred"; // non-numeric text, as an example
bool conversionSucceeded = int.TryParse(userInput, out int pages);
if (conversionSucceeded)
{
Console.WriteLine($"Page count: {pages}");
}
else
{
Console.WriteLine("The text entered is not a valid number.");
}TryParse returns a bool value indicating whether the conversion succeeded, and delivers the
result through an output parameter (out). Don't worry if the out keyword is new to you: for
now it's enough to know that pages will receive the converted number only if
conversionSucceeded is true. In Module 2, when we cover exception handling, you'll better
understand why TryParse is usually preferable to Parse when the data comes from an external
source (such as user input) and we can't guarantee it's valid.
| Method | Behavior on invalid data | When to use it |
|---|---|---|
Convert.ToInt32(text) |
Throws an exception | When you fully trust the data's format |
int.Parse(text) |
Throws an exception | Similar to Convert, type-specific |
int.TryParse(text, out value) |
Returns false, doesn't throw |
When the data might not be valid (for example, user input) |
Constants: const and readonly
Not all data should be allowed to change while the program runs. When a value is fixed and known ahead of time, it's good practice to declare it as a constant, to make clear it will never change and to have the compiler itself prevent any accidental attempt to modify it.
const
Used for values that are already known at compile time and will never change:
const int MaxBooksPerMember = 5;
const string SystemName = "BiblioTech";
Console.WriteLine($"{SystemName} allows a maximum of {MaxBooksPerMember} books per member.");
// MaxBooksPerMember = 10; // ERROR: a constant cannot be modifiedreadonly
Used mostly in the context of classes (Module 3) for values that are set only once, usually when the object is constructed, but that aren't necessarily known at compile time (for example, they might depend on a date or on external input). We only mention it here for comparison; we'll cover its real use in detail in Module 3 when we work with constructors.
const |
readonly |
|
|---|---|---|
| The value is known at... | Compile time | Can be set at run time (only once) |
| Where it's typically used | Local or class variables with a fixed value | Members of a class (Module 3) |
Variable scope
A variable's scope is the region of code where that variable exists and can be used. In
C#, the scope of a local variable is determined by the { } block in which it's declared:
{
int pageCount = 471; // 'pageCount' exists from here on...
Console.WriteLine(pageCount);
} // ...until this block closes
// Console.WriteLine(pageCount); // ERROR: 'pageCount' no longer exists outside the blockThis behavior will become very relevant in Module 2, when we work with if blocks and loops,
since variables declared inside those blocks aren't visible outside them. For now, it's enough
to remember the general rule: a variable lives within the braces where it was declared, and
disappears once that block closes.
Default values and basic nullability
Every value type has a default value it automatically receives if declared without being explicitly initialized (although, in practice, C# requires local variables to be initialized before use, so this concept is more relevant in other contexts, such as a class's fields, which we'll see in Module 3):
| Type | Default value |
|---|---|
int, double, decimal |
0 |
bool |
false |
char |
'\0' (null character) |
string (and other reference types) |
null |
What is null?
null represents the absence of a value: a reference-type variable (such as string) may
not yet point to any data. For example, imagine a BiblioTech book whose ISBN hasn't been
registered yet:
string bookIsbn = null; // the book exists, but doesn't have an ISBN assigned yet
Console.WriteLine(bookIsbn is null); // TrueThe ? symbol for nullable value types
By default, value types (int, bool, etc.) cannot be null: they always have a concrete
value. Sometimes, though, it's useful to represent "not known yet" even for a number — for
example, the page count of a book that hasn't been fully cataloged yet. To do this, C# lets you
turn a value type into a nullable one by adding the ? symbol after the type:
int? pageCount = null; // now this is valid: an 'int?' can have no value
pageCount = 471; // but it can also hold a normal value
Console.WriteLine(pageCount);This is just a first introduction to the idea of nullability; in Module 4 we'll go deeper into
nullable reference types (a more modern feature that extends this same idea to string and
other reference types, helping prevent bugs related to unexpected null values). For now, it's
enough to know that ? after a value type lets you represent "no value yet".
Common Mistakes and Tips
- Trying to use a variable before initializing it: C# doesn't allow reading a local variable that hasn't been initialized; the compiler will raise an error. Always assign an initial value (even a temporary one) before using a variable.
- Losing precision by using
doublefor money: due to its internal binary representation,doublecan carry small inaccuracies in calculations with decimals. For prices, fines, or any monetary amount, always usedecimal. - Using
Parsewith data that might not be valid: if the data comes from outside (user input, a file, an API), useTryParseinstead ofParseorConvert, to keep the program from stopping with an error if the data isn't in the expected format. - Confusing
constwith a normal variable: once a constant is declared, any attempt to modify it is a compile-time error; this is intentional and part of what makes it useful. - Tip: when you're unsure whether to use
varor an explicit type, ask yourself whether the type is obvious just by looking at the assigned value. If the answer is yes,vartends to make the code cleaner; if not, the explicit type helps whoever reads the code afterward.
Exercises
-
Declare three variables to represent a BiblioTech book: its title (
string), its page count (int), and whether it's available (bool). Do it first with explicit types, then rewrite the same three declarations usingvar. Print all three values withConsole.WriteLine. -
A user enters a book's page count as text:
string input = "350";. Write code that safely converts that text tointusingTryParse, and shows a different console message depending on whether the conversion succeeds or not. Then try changing the value ofinputto non-numeric text (for example,"three hundred") and check that the error message is shown correctly. -
Declare a constant
const int StandardLoanDays = 15;representing BiblioTech's usual loan period. Also declare a variableint? extraDays = null;representing a possible loan extension, not yet decided. Write the code needed so that, ifextraDayshas a value assigned, it's added toStandardLoanDaysand the result is shown; if it has no value, onlyStandardLoanDaysis shown. (Hint: you can checkextraDays.HasValueand access the value withextraDays.Value, or use the??operator, which supplies a default value when something isnull:extraDays ?? 0).
Solutions
-
With explicit types:
string bookTitle = "Hopscotch"; int pageCount = 635; bool isAvailable = false; Console.WriteLine(bookTitle); Console.WriteLine(pageCount); Console.WriteLine(isAvailable);With
var(same result, type inferred automatically):var bookTitle = "Hopscotch"; var pageCount = 635; var isAvailable = false; Console.WriteLine(bookTitle); Console.WriteLine(pageCount); Console.WriteLine(isAvailable);
string input = "350";
if (int.TryParse(input, out int pages))
{
Console.WriteLine($"Page count recorded: {pages}");
}
else
{
Console.WriteLine("The value entered is not a valid page count.");
}
If we change input to "three hundred", TryParse will return false and the else
branch will run, showing the error message, without the program stopping abruptly (unlike
what would happen with int.Parse("three hundred"), which would throw an exception).
const int StandardLoanDays = 15;
int? extraDays = null;
int totalDays = StandardLoanDays + (extraDays ?? 0);
Console.WriteLine($"Total loan days: {totalDays}");
extraDays = 5; // now an extension is assigned
totalDays = StandardLoanDays + (extraDays ?? 0);
Console.WriteLine($"Total loan days (with extension): {totalDays}");
The ?? operator (called the null-coalescing operator) returns the value on the left if
it's not null, or the one on the right otherwise. So, when extraDays is null, 0 is
added; when it has a value, that value is added.
Conclusion
In this lesson you learned to declare variables with explicit types and with var, to tell
value types and reference types apart, to safely convert data from one type to another, to
define constants, to understand a variable's scope, and to take your first steps with basic
nullability. All of these concepts are the foundation all the code you write in the coming
modules will rest on, starting with the classes we'll define in Module 3 (Book, Member,
Loan), which are nothing more than an organized set of variables (called there "fields" or
"properties") along with their associated behavior.
In the next and final lesson of this module, Arrays and Strings, you'll learn to work with
collections of data (for example, several book titles at once) using arrays, and to
manipulate text in depth with the string class, including interpolation, searching, and
common transformations.
C# Programming Course
Module 1: Introduction to C#
- Introduction to C#
- Setting Up the Development Environment
- Hello World Program
- Basic Syntax and Structure
- Variables and Data Types
- Arrays and Strings
Module 2: Control Structures
Module 3: Object-Oriented Programming
- Classes and Objects
- Methods
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- Structs and Records: Value Types and Reference Types
Module 4: Advanced C# Concepts
- Interfaces
- Delegates and Events
- Pattern Matching and Modern C# Features
- Generics
- Collections
- LINQ (Language Integrated Query)
- Asynchronous Programming
Module 5: Working with Data
- File I/O
- Serialization
- Database Connectivity
- Entity Framework
- Working with JSON and Consuming REST APIs
Module 6: Advanced Topics
- Reflection
- Attributes
- Dynamic Programming
- Memory Management and Garbage Collection
- Multithreading and Parallel Programming
Module 7: Building Applications
Module 8: Best Practices and Design Patterns
- Coding Standards and Best Practices
- Design Patterns
- Dependency Injection and Inversion of Control
- Unit Testing
- Code Review and Refactoring
