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

  1. Declaring variables: explicit types and var
  2. Value types
  3. Reference types
  4. Type conversion
  5. Constants: const and readonly
  6. Variable scope
  7. 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:

type variableName = initialValue;

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:

  • double is faster and takes up less space, but can have small inaccuracies in certain operations (due to how it represents numbers internally in binary).
  • decimal is 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 letter m at 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:

string bookTitle = "One Hundred Years of Solitude";
string isbn = "978-0307474728";

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 affected

With 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 loss

Explicit 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 modified

readonly

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 block

This 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); // True

The ? 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 double for money: due to its internal binary representation, double can carry small inaccuracies in calculations with decimals. For prices, fines, or any monetary amount, always use decimal.
  • Using Parse with data that might not be valid: if the data comes from outside (user input, a file, an API), use TryParse instead of Parse or Convert, to keep the program from stopping with an error if the data isn't in the expected format.
  • Confusing const with 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 var or an explicit type, ask yourself whether the type is obvious just by looking at the assigned value. If the answer is yes, var tends to make the code cleaner; if not, the explicit type helps whoever reads the code afterward.

Exercises

  1. 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 using var. Print all three values with Console.WriteLine.

  2. A user enters a book's page count as text: string input = "350";. Write code that safely converts that text to int using TryParse, and shows a different console message depending on whether the conversion succeeds or not. Then try changing the value of input to non-numeric text (for example, "three hundred") and check that the error message is shown correctly.

  3. Declare a constant const int StandardLoanDays = 15; representing BiblioTech's usual loan period. Also declare a variable int? extraDays = null; representing a possible loan extension, not yet decided. Write the code needed so that, if extraDays has a value assigned, it's added to StandardLoanDays and the result is shown; if it has no value, only StandardLoanDays is shown. (Hint: you can check extraDays.HasValue and access the value with extraDays.Value, or use the ?? operator, which supplies a default value when something is null: extraDays ?? 0).

Solutions

  1. 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.

© Copyright 2026. All rights reserved