Variables in C# Introduction
Table of Contents
- Description of a C# variable
- Characteristics of a variable
- Declaration of Variables
- Types of variables and their locations
- Field variables
- Lambda function variables
- Local variables
- Method local variables
- Constructor local variables
- Property accessor local variables
- Loop variables
- Parameter variables
- Pattern matching variables
- Property backing field variables
- Tuple deconstruction variables
- Variable scoping
- Types of variable scopes and their locations
- Block scope
- Class, record & struct scope
- Local scope
- Constant variables
- Summary
In this article we will outline what variables are in C#, and their roles in building programs. We will also cover the rules for naming and scoping variables, as well as explore constants, readonly fields, and more. Continue reading to explore how variables work in C#.
Description of a C# variable
Variables in C# are named storage locations which contain data to be used in your programs. Variables may be updated (mutable) throughout the execution of your program, or alternatively may be defined as immutable. Immutability is a long existing mathematical concept which essentially means unchanging. In programming it refers to variables which cannot be modified once declared.
Characteristics of a variable
Many educational resources will only show you examples of local variables, thereby omitting the other kinds listed further on in this article. However, those are also technically variables, as they meet all of the characteristics listed below:
- A variable stores data in a named location
- A variable has a type
- A variable holds a value
- A variable is declared within a specific scope, such as a method, class, record, or struct, depending on its kind (e.g. local, field, parameter)
Declaration of Variables
When you see the term declare, think of it as stating the name and type of the variable.
int count = 42;
In the above example, we are declaring a variable of type int, named count, and the data or value we are storing is the number 42.
Note that I did not say value when I mentioned declaration, only name and type. This is because you can declare variables uninitialised, which means that no value is initially set by your statement. Therefore, you can declare a variable and initialise it with a value, or declare it and leave it uninitialised with a default value.
The default values differ depending on what type you are declaring.
int count = default;
string name = default;
In the example above, both string and int variables are set to their default value using the default keyword. As a result, both variables will be set to their default values. For int it is 0, and for the string it is null. We will discuss null in a future article.
Types of variables and their locations
Below is a table outling the various types of variables, and where they are located in C# programs.
Variable kind | Location |
---|---|
Field | Classes, Records, Structs |
Lambda function variables | Lambda functions |
Local variables | Constructors, Methods, Property accessors |
Loop variables | for, foreach, and while loops |
Parameter variables | Constructors, Lambda functions, Methods |
Pattern matching variables | Methods |
Property backing field | Properties |
Tuple deconstruction variables | Tuples |
Field variables
Field variables are particularly important to understand. Field variables can be either instance-level, which means a new storage location is created for the variable for every instance of the object, or they can be static, which means that one variable is shared across all instances of an object. We will outline more about instance and static fields in future articles.
Furthermore, variables can be mutable or immutable. Declaring a field variable as immutable is accomplished with the readonly keyword. The absence of the readonly keyword indicates that the field variable is mutable.
public class LeaveTracker
{
public static readonly int daysInCalendarYear = 365;
public readonly int maxNumberOfDaysAnnualLeave;
public LeaveTracker(int maxNumberOfDaysAnnualLeave)
{
this.maxNumberOfDaysAnnualLeave = maxNumberOfDaysAnnualLeave;
}
}
In the example above, you have two variables, maxNumberOfDaysAnnualLeave and daysInCalendarYear. Both are immutable, as they are defined with the readonly keyword. maxNumberOfDaysAnnualLeave is an instance variable, therefore a new memory location will be created for every instance of the LeaveTracker class created.
daysInCalendarYear however has been declared as static, therefore it is a static variable, and will only have one memory location that will be shared for all instances of the LeaveTracker class created.
Lambda function variables
Lambda function variables are variables that are declared inside a lambda function.
Func<int, int> squarePlusOne = value =>
{
int squaredValue = value * value;
return squaredValue + 1;
};
int result = squarePlusOne(5);
In the example above, you can see a value named squaredValue declared inside the lambda function, which is used to store the computed squaring of the value parameter for later use.
Local variables
Local variables are defined within constructors, methods, or property accessors. These local variables are only accessible within those constructs. This is otherwise known as their scope, or where they may be accessed/referenced.
Method local variables
Method variables are variables that are declared inside a method block. Think of the method block as anything inside the enclosing braces, or { }.
void Shout()
{
string text = "Hello, World!";
Console.WriteLine(text);
}
In the example above, there is a method named Shout, and inside the method block there is a string variable named text, which is used to store the text, Hello, World! The method then writes that text to the console/terminal via the Console.WriteLine function.
Constructor local variables
Constructor variables are any variables declared inside the constructors of classes, records or structs.
public class Instrument
{
public static int MaxDecibels { get; }
static Instrument()
{
int max = 85;
MaxDecibels = max;
}
public string DisplayName { get; }
public Instrument(string name)
{
string displayName = "Instrument: " + name;
DisplayName = displayName;
}
}
In the example above, there are two constructors for the Instrument class.
One constructor is static and the other is an instance constructor. Static in this example essentially means the static constructor runs once, before any instances are created, whereas instance constructor will be executed each time a new Instrument class is instantiated.
In the static constructor we define a max variable to store the integer value for the maximum decibel value for an instrument. Then we assign that max variable to the MaxDecibels static property. The MaxDecibels property must be static, otherwise the static constructor would not be able to assign the value.
Property accessor local variables
Property accessor variables are variables which are defined inside property get or set blocks.
public class Robot
{
private readonly string name;
public Robot(string name) {
this.name = name;
}
public string DisplayName
{
get
{
string prefix = "Name: ";
return prefix + name;
}
}
}
In the example above, there is a DisplayName class property which is get only. In the property body there is a prefix string variable declared, and is used to concatenate the word Name: on to the name that is passed in the Robot constructor.
Loop variables
Loop variables are variables that have been declared and are to be used in the various C# looping constructs, such as for, foreach, while & do-while.
For loop variables
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
In the example above, the integer variable i is declared with a value of 0. As the loop body executes, the value of i is output to the console/terminal using the Console.WriteLine function.
Foreach loop variables
string[] fruits = { "Apple", "Orange", "Pear" };
foreach (string fruit in fruits)
{
Console.WriteLine(fruit);
}
In the example above, a list of fruits is defined, and then for each iteration of the fruits list in the foreach loop, a string variable named fruit is declared. That variable is scoped to each iteration of the loop. For each iteration of the loop, the fruit will be a new value from the fruits list.
While loop variables
int count = 0;
while (count < 10)
{
Console.WriteLine(count);
count++;
}
In the example above, an integer count variable is defined. The variable is used as the condition for looping in the while loop. Incrementing the count variable in the while loop body using count++ ensures that the loop will not go on indefinitely.
Do-while loop variables
int count = 0;
do
{
Console.WriteLine(count);
count++;
} while (count < 10);
In the example above, similar to the while loop, the do-while loop executes code continuously until the count variable no longer matches the condition.
Parameter variables
Parameter variables are variables which are defined and scoped to constructors, lambda functions, and methods, in classes, records, and structs.
Constructor parameter variables
Constructor parameter variables are located within the constructors of classes, records and structs.
public class StellarObject
{
public string Designation { get; }
public StellarObject(string designation) {
Designation = designation;
}
}
In the example above, there is a StellarObject, along with a constructor with a designation string parameter variable declared. The designation parameter is then assigned to the Designation property.
The scope of the designation parameter is limited to the constructor only. It can not be accessed anywhere else in the class.
Lambda function parameter variables
Lambda function parameter variables are variables which are defined as inputs to lambda functions.
Func<int, int, int> add = (a, b) => a + b;
int sum = add(3, 4);
In the example above, you have a lambda function add, which takes two integer parameters a and b, and those parameters are added together to reproduce a result.
The type on the lambda function parameters is optional in this case, as it can be inferred by the compiler.
Func<int, int, int> add = (int a, int b) => a + b;
int sum = add(3, 4);
As with the previous example, this lambda function takes two parameters a and b, although this case, the types have been defined explicitly.
Method parameter variables
Method parameters are parameters which are defined as inputs to methods.
void Shout(string message) {
Console.WriteLine(message);
}
In this version of the Shout method, we have defined a message string parameter, so that the method can take in the message that can be output to the console/terminal using the Console.WriteLine method.
Pattern matching variables
Pattern matching variables are variables declared in pattern matching expressions.
is type checking pattern matching variables
is type checking pattern matching variables are variables which are declare as the result of a pattern matching operation for a type.
object couldBeAString = "Hello, World!";
if (couldBeAString is string message)
{
Console.WriteLine(message);
}
In the example above, we define an object with the string value of Hello, World!. As it has been declared as an object, code that uses the object has no way to determine what the type could be. Therefore, using pattern matching in an if expression to only output to the console/terminal if the couldBeAString is actually a string, makes the new message variable in the Console.WriteLine method.
is property pattern matching variables
is property pattern matching variables are variables defined in a match expression's properties or fields.
record Individual(string FirstName, string Surname);
Individual individual = new Individual("John", "Smith");
if (individual is { FirstName: var first })
{
Console.WriteLine($"First name is {first}");
}
In the example above, the property pattern match expression on the individual record variable allows us to use the first variable in the Console.WriteLine method to output to the console/terminal.
Switch statement type pattern matching variables
Switch statement type pattern matching variables are variables that are defined as part of the case statement when switching on a variable and matching on the type.
object input = 99;
switch (input)
{
case int number:
Console.WriteLine($"Number: {number}");
break;
case string text:
Console.WriteLine($"Text: {text}");
break;
}
In the example above, the variable input is defined as an object, therefore code that uses the object has no way to determine what the type could be.
The switch expression above has a case statement that will execute if the object is a number and provides an integer typed variable named number that is used to output a number message to the console/terminal using the Console.WriteLine method.
Switch expression pattern matching variables
Switch expression type pattern matching variables are variables that are defined as part of the expressions when switching on a variable and matching on the type.
object input = 99.9;
string output = input switch
{
int i => $"Integer {i}",
double d => $"Double {d}",
_ => "Unknown"
};
Console.WriteLine(output);
In the example above, the variable input is defined as an object, therefore code that uses the object has no way to determine what the type could be.
The output variable above is used to output to the console/terminal the type of the number, followed by the number itself, by using the Console.WriteLine method.
This result comes from the switch expression assigned to the output variable, which has the possibility to match on either integers or doubles. If the type is not those two, then the output will contain the text, Unknown.
In the integer and double types, you can see that if the match is made, a variable of either i or d are available to be used in the matched statement.
Property backing field variables
Property backing field variables are not immediately visible. They are a form of syntactic sugar to enable support for auto-properties. For each property, the compiler generates a hidden backing field.
public string Title { get; set; }
You can see above that there is a normal auto-property named Title
Behind the scenes, the compiler generates a hidden backing field, which the get and set of the Title property uses to retrieve and store data for the property.
Below is a conceptual approximation of what the compiler generates, not actual output:
private string Titlek__BackingField;
public string Title
{
get => Titlek__BackingField;
set => Titlek__BackingField = value;
}
In the approximation example above, you can see that the get and set of the Title property is delegating retrieval and storage to the Titlek__BackingField private field.
You do not see this in your code editor when you create an auto-property, but it happens behind the scenes during compilation.
Tuple deconstruction variables
Tuple deconstruction variables are variables introduced when you deconstruct a tuple.
var person = ("John", 43);
var (name, age) = person;
Console.WriteLine($"Name: {name} - Age: {age}");
In the example above, we construct a person tuple that contains a name and age. We then deconstruct the tuple into two variables, named name and age.
We then output the variables to the console/terminal using the Console.WriteLine method.
(string firstName, int ageInYears) = ("Jack", 42);
Console.WriteLine($"{firstName} is {ageInYears} years old.");
In the example above we have deconstructed a tuple which takes in a first name and an age, into two variables with their types explicitly declared, named firstName and ageInYears.
We then output the variables to the console/terminal using the Console.WriteLine method, in a sentence.
Variable scoping
Variable scope refers the section of code where a variable is accessible and usable. A variable's scope is determined by where it is declared, and this affects how and where the variable can be referenced in your code.
Understanding variable scoping helps avoid naming conflicts, which result in compiler errors, and unintended value changes which can lead to unpredictable behaviour, such as logic bugs.
Types of variable scopes and their locations
Below is a table outlining the types of variable scopes, and their location in a C# program.
Scope kind | Location |
---|---|
Block scope | Inside { } blocks |
Class/Record/Struct scope | Across all members of a class/record/struct |
Local scope | Constructors, methods, and property accessors |
Block scope
Variables declared inside a code block, denoted by braces { } are scoped to that block.
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
Console.WriteLine(i); // ❌ 'i' does not exist in this context
In the example above, the variable i is declared as part of the for loop. The scope of that variable is limited to the block enclosing the for loop, denoted by the braces { }.
The second statement for Console.WriteLine(i) does not have access to the i variable, therefore the C# program is not valid, and the compiler would report an error.
Class, record & struct scope
Variables declared inside a class/record or struct, but outside a constructor or method, are known as fields, and are scoped to all members of that class/record/struct, and if they have been declared as public, outside as well.
public class Clicker
{
private int clicked = 0;
public void Click()
{
clicked++;
}
public int GetClicked()
{
return clicked;
}
}
In the example above we have a field variable named clicked. It was declared at the class level, therefore it is accessible across within the whole class. In this example, it is in the constructor, and the GetClicked method.
Local scope
Variables declared inside a constructor, method or property accessor are local to that construct, and cannot be accessed outside of it.
void Shout()
{
string message = "Hello!";
Console.WriteLine(message);
}
Console.WriteLine(message); // ❌ 'message' does not exist in this context
As with block scope, the the message variable is only accessible between the braces { }, but this time it for the braces enclosing the Shout method.
Again, the second statement for Console.WriteLine(message) does not have access to the message variable, therefore the C# program is not valid, and the compiler would report an error.
Constant variables
At first glance, combining the word const (constant) with variable may seem contradictory. Const variables in C# however refer to immutable compile-time variables.
In simpler terms, const variables are variables in which the value is known at compile time. This means that the value must be assigned either a primitive type such as int, double, float, a char, a string etc., an enum, or an expression of either of those previous.
Furthermore the value must be assigned in your code directly at compile time, so that the compiler may understand its value.
The compiler may choose to inline your constant variable depending on whether it considers inlining it an optimization. Inlining means replacing usages of your constant within the literal value. So instead of a constant helloWorld, with a value of Hello, World!, the compiler would replace usages of the helloWorld variable with the literal value directly. In our example it would be the string, Hello, World!.
public const int AgeInYears = 99;
public const double Pi = 3.14159;
public const string Forename = "Dan";
public const char NewLine = '\n';
public const Month FirstMonth = Month.January;
Summary
This is just the beginning of your journey into variables in C#. You will find in subsequent articles that we dive deep into all of the language features we have described in this article.
Furthermore, as we build out the Illumonos content on C#, we will update parts of this article with references to any article on Illumonos which goes deeper into a particular variable concept.