If we were to take the work we’ve done so far and try to write a program, we couldn’t do much with it. See, we’ve done a lot of work in simplifying values, but don’t get me wrong, is important! However, notice even when describing some of the more hands on approaches in evaluating booleans, we ended up in a situation where we had to label things. This chapter will dive into this idea of labeling and remembering values, exploring

  1. how we bind values to a label so that they become reusable,
  2. whether these bindings are permanent or variable,
  3. and how we define where we can use them.

Defining Our Terms

First, we introduce what we call these labeling/bindings: variables.

Variables

Definition

A Variable is a reserved space in memory used to store a value of some type.

When we want to label a value in programming, we usually create a variable for it. We refer to giving a value to this variable as binding or assigning the value to it.

Properties

  • Memory Management: Types define the exact amount of memory space needed for each variable, allowing C# to efficiently allocate and manage computer resources
  • Program Reliability: Fixed types prevent unexpected behavior by ensuring operations (like division) consistently work as intended across your code

Example

int x = 7; // reads: create an int variable called x, and assign 7 to it.

Notice that we use the = symbol here. In C# = is used for assignment, where is == is used to check for equality.

Memory Management

When you create a variable, C# needs to plan ahead for the space it’ll occupy in memory. It’s like reserving a specific size storage unit - you need to know exactly how much space you’ll need:

int age = 25;     // 32 bits reserved
double salary = 50000.00;  // 64 bits reserved

Memory Allocation

Each type declaration gives C# precise instructions for memory allocation. No guesswork needed - just clean, efficient storage.

Type Safety

Type consistency ensures your operations behave predictably across your codebase. Consider division:

// Integer division
int result = 5 / 2;  // 2
// Float division
float precise = 5f / 2f;  // 2.5

Type Safety

The guarantee that operations will behave consistently based on their types, preventing unexpected bugs and crashes.

Practical Impact

In real applications, type safety becomes crucial. Financial calculations, data processing, or any situation where precision matters - having guaranteed behavior makes your code reliable and maintainable.

Think of types as both an organizational system and a safety net. They help C# manage memory efficiently while ensuring your operations remain consistent and predictable throughout your code.


Now you may have noticed that we use the word variable. This seemingly implies that the value held can, well, vary. This is true! C#‘s variables may be re-assigned/bound by default. To cover cases where this is not desired, the const keyword is used.

Example

int x = 7;
x = 15; // this is valid
// However...
const int y = 7;
y = 0; // this will cause an error

We now have this idea that a variable’s value can change throughout the execution of your program. After line 1, x == 7 would return true, however after line 2 it would return false. This means if I hand you this code and ask: “What value does x have”, both 7 and 15 would be valid answers. To handle this, when we talk about code, we need to be specific about the place in our program we are referring to. This allows us to talk about our code in different states.

State

Definition

Program state is the set of bindings at a given point of execution.

We can refer to the set of variables and their current values as our bindings.

Example

int x = 37;
int y = 12;
bool isBigger = false;
 
isBigger = x <= y;
 
x = 12
isBigger = x <= y; 

After line 1 runs, 37 is bound by x. After line 2, 12 is bound by y. Depending on if we check after line 3 or line 8 runs, isBigger will be either false or true respectively.

To handle this we introduce using a table to track the program state:

after line # runsxyisBigger
137--
23712-
33712false
53712false
71212false
81212true

This let’s us talk about what the data in our program is like at any point, which, now that this can change wildly line per line, seems pretty handy to me. This also let’s us do something nice: we can now look at how this data changes to better understand what our code is doing. Instead of a bunch of expressions we try to follow, we can also look at how the state of our program changes over time to better understand how it works. This is incredibly useful when debugging code, where we often end up programming behavior wrong, and the only way to see that is to find that our stored values are off!

Now there may be some question about how this works under the hood. Well, notice that when we define a variable, we must give it a type. This is important: Programming languages use types not only to define what kind of value something will be, but also how much space it will take. So when we create a variable, C# first looks at the type, remember an int is a 32bit whole number, right? Well, C# will find a 32-bit chunk of memory and reserve it for your variable. We also need to name it: this name is how C# finds the variable in memory, this is how it determines its address.

However, this raises a question: if we are reserving space in memory for these variables, how/when are they ever freed?

C# uses what’s called a garbage collector, to detect when a variable will not be used again in the program, “releasing” it from memory thereafter.

C# also recognizes that it can be nice to have manual control over this as well, providing tools to allow us to control where a variable will be available explicitly, rather than letting the garbage collector take all the fun from us. It does so using the concept of define scopes.

Scope

Definition

Program scope hold boundaries of code, with a defined start and end, where any new entries to the programs state are cleared once execution crosses the end.

This means that any variable, or other binding, we create within a specific scope is only accessible within, and inaccessible from the outside.

curly braces {} to define the bounds of scope.

{
int x = 17;
x > 1; // this works
}
 
{
int y = 18
y = x; // this doesn't, x doesn't exist outside of the scope it belongs to
}

Using Variables With Expressions

Foundational Concepts

  • Expressions evaluate to values - This remains true even when variables are involved
  • Assignment requires full evaluation - Right-hand side resolves completely before storage
  • Type compatibility is mandatory - Evaluated result must match variable’s declared type
  • Variables act as value proxies - Substitute their stored value in expressions

Variables in Expressions

Let’s revisit our core definition:

Definition

An expression is a set of values and operators which evaluate to become a single value.

Variables integrate seamlessly with this definition. When a variable appears in code:

int x = 32;
int y = x; // What occurs here?

Execution sequence:

  1. Create integer x storing 32
  2. Create integer y
    • Evaluate x → 32
    • Store result in y

This behavior leads us to recognize:

  • Variables are evaluable entities in expressions
  • Using a variable retrieves its current value

The Assignment Process

Our updated definition expands expression components:

Definition

An expression is a set of operators and/or entities which combine to evaluate to a single value.

This directly informs assignment mechanics:

Assignment

Definition

Before we assign or bind a value to a variable, the expression is fully evaluated.

Example

int x = 32 + 7;

Execution steps:

  1. Allocate int storage named x
  2. Evaluate 32 + 7 → 39
  3. Store 39 in x

This “evaluate first, store second” principle ensures expressions always resolve to concrete values before assignment.

Type Safety Enforcement

Consider this type mismatch:

int x = "hello"; // Invalid assignment

The compiler error:

error CS0029: Cannot implicitly convert type 'string' to 'int'

This demonstrates type safety - the system prevents using incompatible types, analogous to needing specific tools for particular tasks. Key benefits:

  • Predictable program behavior
  • Prevention of invalid operations
  • Clear error diagnostics

Expression Evaluation in Practice

Combining variables in complex expressions:

// Rectangle dimensions
int length = 5;
int width = 7;
int rectangle_area = length * width;

Evaluation sequence:

  1. Evaluate length → 5
  2. Evaluate width → 7
  3. Multiply results → 35
  4. Store 35 in rectangle_area

This demonstrates our fundamental rule:

Important

Evaluation Priority
Variables always resolve to their current values before any operations execute.



Core Principles:

  1. Variables serve as value proxies in expressions
  2. Complete evaluation precedes any assignment
  3. Type compatibility is rigorously enforced
  4. Complex expressions resolve variables before performing operations

In general, you will find that many errors you run into come from not considering what order things will evaluate in. Now that we have this concept of program state and storing values, how do we create programs that change behavior based on these variables? Next chapter we will cover this, going over Branching as a control flow structure.