So far we've worked with variables that store a single piece of data: a title, a page count, a
boolean. But in practice, almost any program needs to handle several pieces of data of the
same type at once — for example, all of BiblioTech's book titles — and also needs to
manipulate text in a more advanced way than a simple Console.WriteLine. In this lesson
you'll learn to work with arrays (single- and multi-dimensional), to iterate over them with
foreach/for, and to make the most of the string class: concatenation, interpolation, and
its most common methods. We'll wrap up with StringBuilder, the right tool when you need to
concatenate text repeatedly inside a loop.
Contents
- Single-dimensional arrays
- Multi-dimensional arrays
- Fixed-size arrays versus
List<T> - Iterating over an array with
foreachandfor - The
stringclass: immutability, concatenation, and interpolation - Common
stringmethods StringBuilderfor efficient concatenation
Single-dimensional arrays
An array is a collection of elements of the same type, stored contiguously and accessed
through a numeric index starting at 0. It's declared by specifying the elements' type
followed by square brackets []:
string[] bookTitles = new string[3];
bookTitles[0] = "One Hundred Years of Solitude";
bookTitles[1] = "Hopscotch";
bookTitles[2] = "Ficciones";
Console.WriteLine(bookTitles[0]); // One Hundred Years of SolitudeIt can also be declared and initialized in a single step, specifying the values directly:
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones" };
// Equivalent, more explicit form:
string[] bookTitlesB = new string[] { "One Hundred Years of Solitude", "Hopscotch", "Ficciones" };Fixed size and the Length property
A fundamental characteristic of arrays in C# is that their size is fixed when they're created
and cannot change afterward. To find out how many elements it contains, you use the Length
property:
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones" };
Console.WriteLine(bookTitles.Length); // 3Out-of-range access
Trying to access an index that doesn't exist (for example, bookTitles[5] in an array with 3
elements) doesn't cause a compile error, but it does cause a runtime error (an
IndexOutOfRangeException, which we'll study in Module 2 when we cover exception handling).
That's why you must always make sure the index used is between 0 and Length - 1.
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones" };
// Console.WriteLine(bookTitles[3]); // Runtime error: index out of range
Console.WriteLine(bookTitles[bookTitles.Length - 1]); // Ficciones (the last element)Multi-dimensional arrays
Besides single-dimensional arrays (a simple list of elements), C# allows multi-dimensional arrays, useful for representing data organized as a table or grid. The most common case is the two-dimensional array (rows and columns):
// An array of 2 rows (shelves) x 3 columns (slots per shelf)
string[,] shelf = new string[2, 3];
shelf[0, 0] = "One Hundred Years of Solitude";
shelf[0, 1] = "Hopscotch";
shelf[0, 2] = "Ficciones";
shelf[1, 0] = "The Aleph";
shelf[1, 1] = "Pedro Páramo";
shelf[1, 2] = "The House of the Spirits";
Console.WriteLine(shelf[1, 2]); // The House of the SpiritsIt can also be initialized directly with its values:
string[,] shelf =
{
{ "One Hundred Years of Solitude", "Hopscotch", "Ficciones" },
{ "The Aleph", "Pedro Páramo", "The House of the Spirits" }
};To find the number of rows and columns in a two-dimensional array, you use the GetLength
method, specifying the dimension (0 for rows, 1 for columns):
Console.WriteLine(shelf.GetLength(0)); // 2 (rows / shelves)
Console.WriteLine(shelf.GetLength(1)); // 3 (columns / slots per shelf)In this course, multi-dimensional arrays will appear only occasionally; most examples will use single-dimensional arrays, which are by far the most common in everyday use.
Fixed-size arrays versus List<T>
As we've seen, an array has a fixed size: once created with, say, 3 elements, you can't add a fourth book title without creating a completely new array. In many real scenarios (such as gradually adding books to the BiblioTech catalog as they're registered), this limitation is inconvenient.
For those cases, .NET offers List<T>, a collection whose size can grow and shrink
dynamically, with methods like Add (to add an element) or Remove (to remove it). We only
mention it in passing here: List<T> and the rest of .NET's collections are studied in depth
in Module 4 ("Collections"), together with LINQ, the powerful query tool we'll use to explore
BiblioTech's full catalog. For now, keep this in mind:
Array (string[]) |
List<T> (for example, List<string>) |
|
|---|---|---|
| Size | Fixed when created | Dynamic (grows and shrinks) |
| When to use it | The number of elements is known ahead of time and doesn't change | The number of elements can vary at runtime |
| Studied in detail in | This module | Module 4 |
Iterating over an array with foreach and for
To process every element of an array, one by one, there are two main loops. Although loops are studied in depth in Module 2 ("Control Flow"), here we'll see their basic use applied to arrays, since it's such a common combination that it's worth knowing right away.
foreach: iterating over every element
foreach iterates over every element of a collection, one after another, without needing
to manually manage an index:
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones" };
foreach (string title in bookTitles)
{
Console.WriteLine(title);
}This code prints each title in the array, one per line. foreach is the clearest and
recommended option when you simply need to read or process each element, without needing
its position (index).
for: iterating with explicit control over the index
When you do need the index (for example, to number the books, or to modify the array itself
while iterating), you use the for loop, which gives you explicit control over a counter:
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones" };
for (int i = 0; i < bookTitles.Length; i++)
{
Console.WriteLine($"{i + 1}. {bookTitles[i]}");
}This example prints each title preceded by its position in the list, starting at 1 (adding 1 to the index, which starts at 0):
foreach |
for |
|
|---|---|---|
| Access to the index | No (only each element's value) | Yes, through the counter (i) |
| Modifying the array while iterating | Not recommended | Possible, with care |
| Readability | Simpler and more direct | More flexible but somewhat more verbose |
The string class: immutability, concatenation, and interpolation
Immutability
A string in C# is immutable: once created, its content can't be changed. Any operation
that "seems" to change a string (such as concatenating it with other text) actually creates a
new string in memory, leaving the original untouched:
string originalTitle = "Hopscotch";
string modifiedTitle = originalTitle + " (special edition)";
Console.WriteLine(originalTitle); // Hopscotch (unchanged)
Console.WriteLine(modifiedTitle); // Hopscotch (special edition)This property has important performance implications when concatenating many strings inside a
loop, as we'll see later in the StringBuilder section.
Concatenation
Concatenating means joining two or more strings into one. The most basic way is with the +
operator:
string title = "One Hundred Years of Solitude";
string author = "Gabriel García Márquez";
string description = title + " - " + author;
Console.WriteLine(description); // One Hundred Years of Solitude - Gabriel García MárquezString interpolation ($"")
Interpolation is a much more readable way to build strings that combine fixed text with
variable values. You put a $ symbol before the quotes, and variables (or even expressions)
are inserted directly inside braces { }:
string title = "One Hundred Years of Solitude";
int pages = 471;
bool available = true;
string summary = $"The book '{title}' has {pages} pages and is available: {available}";
Console.WriteLine(summary);Result:
Inside the braces { } of an interpolated string you can even write small expressions, not
just plain variables:
int pagesRead = 120;
int totalPages = 471;
Console.WriteLine($"Reading progress: {pagesRead} of {totalPages} pages ({pagesRead * 100 / totalPages}%)");| Technique | Example | Readability |
|---|---|---|
Concatenation with + |
"Book: " + title + ", pages: " + pages |
Gets hard to read with many variables |
Interpolation $"" |
$"Book: {title}, pages: {pages}" |
Clear even with several variables |
In this course we'll prefer interpolation whenever we combine text with variables, for the sake of clarity.
Common string methods
The string class includes numerous built-in methods for the most common text manipulation
tasks. Here are the most widely used:
| Method | What it does | Example |
|---|---|---|
Split |
Splits a string into an array of substrings, based on a separator | "a,b,c".Split(',') → ["a", "b", "c"] |
Trim |
Removes whitespace from the start and end | " Hopscotch ".Trim() → "Hopscotch" |
Contains |
Checks whether a string contains another | "Hopscotch".Contains("psc") → true |
Substring |
Extracts a portion of the string, given a starting position (and optionally a length) | "Hopscotch".Substring(0, 3) → "Hop" |
ToUpper / ToLower |
Converts the whole string to uppercase or lowercase | "Hopscotch".ToUpper() → "HOPSCOTCH" |
Let's see them applied to a realistic BiblioTech example: processing a line of text with several pieces of book data separated by commas, similar to how it might come from an import file (something we'll cover in depth in Module 5, "File Input/Output").
string importedLine = " Hopscotch, Julio Cortázar, 635 ";
string cleanLine = importedLine.Trim(); // removes leading/trailing whitespace
string[] fields = cleanLine.Split(','); // splits on commas: [" Hopscotch", " Julio Cortázar", " 635 "]
string title = fields[0].Trim(); // "Hopscotch"
string author = fields[1].Trim(); // "Julio Cortázar"
string pagesText = fields[2].Trim(); // "635"
Console.WriteLine($"Title: {title}");
Console.WriteLine($"Author: {author}");
Console.WriteLine($"Pages (text): {pagesText}");
// Searching for text within a title
bool containsScot = title.Contains("scot");
Console.WriteLine($"The title contains 'scot': {containsScot}");
// Extracting the first 3 letters of the title
string titlePrefix = title.Substring(0, 3);
Console.WriteLine($"Title prefix: {titlePrefix}");
// Normalizing to uppercase for case-insensitive comparisons
string titleUppercase = title.ToUpper();
Console.WriteLine(titleUppercase); // HOPSCOTCHNotice that none of these methods modifies the original string (remember: strings are immutable); instead, each one returns a new string with the result. That's why we always assign the result to a variable (or use it directly), instead of expecting the original variable to change on its own.
Comparing strings while ignoring case
A very common use case when searching a catalog (like BiblioTech's) is comparing text without
caring about uppercase or lowercase. To do this, you can convert both strings to the same case
before comparing, or use the Equals method with a specific option:
string searchTerm = "hopscotch";
string catalogTitle = "Hopscotch";
bool matches = catalogTitle.Equals(searchTerm, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(matches); // TrueStringBuilder for efficient concatenation
Since every string is immutable, repeatedly concatenating strings inside a loop (for example,
to build a report with every title in the catalog) creates, on every iteration, a whole new
string in memory, discarding the previous one. For a handful of elements this isn't a problem at
all, but if the loop runs many, many times (thousands of books, for example), it can noticeably
hurt performance.
For these cases, .NET offers the StringBuilder class, designed specifically to build strings
efficiently through successive modifications, without creating a whole new string at every
step. It lives in the System.Text namespace.
using System.Text;
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones", "The Aleph" };
StringBuilder report = new StringBuilder();
report.Append("BiblioTech catalog:");
report.AppendLine(); // line break
foreach (string title in bookTitles)
{
report.AppendLine($"- {title}");
}
string finalReport = report.ToString();
Console.WriteLine(finalReport);Result:
| Situation | Recommendation |
|---|---|
| Concatenating a few strings, outside a loop | + or $"" interpolation |
| Concatenating inside a loop that may run many times | StringBuilder |
We won't go any deeper into StringBuilder in this introductory module; it's a tool that will
naturally keep coming up later in the course, especially when we work with large book catalogs
and report generation.
Common Mistakes and Tips
- Accessing an out-of-range index: remember that an array's indices run from
0toLength - 1. Accessingarray[Length]is a very common mistake and causes a runtime exception. - Expecting an array to change size: arrays have a fixed size. If you need to add or remove
elements dynamically, you'll need
List<T>(Module 4), not an array. Creating a bigger array and copying the data over is possible, but cumbersome and uncommon in practice. - Forgetting that strings are immutable: writing
title.ToUpper();without assigning the result to any variable doesn't modifytitle; you need to writetitle = title.ToUpper();(or use the result directly wherever it's needed). - Using
+concatenation inside large loops: for a handful of elements it's fine, but if the loop processes a huge number of elements,StringBuilderis preferable. - Using
Splitwithout cleaning up whitespace: when splitting text withSplit, each fragment may carry extra whitespace; it's a good idea to applyTrim()to each one, as we did in the data-import example. - Tip: when working with text that combines variables, always prefer interpolation
(
$"...") over+concatenation: the resulting code is much more readable and less prone to errors.
Exercises
-
Declare a
string[]array with at least 5 BiblioTech book titles. Iterate over it withforeachand display each title on its own line. Then iterate over it with aforloop, showing each title numbered, starting at 1 (for example:1. One Hundred Years of Solitude). -
Given the array of titles from the previous exercise, write code that iterates over the array and shows only the titles that contain the letter
"a"(using theContainsmethod, converting the title to lowercase withToLower()before comparing, so the search is case-insensitive). -
Using
StringBuilder, build a text report with all the titles from the array in exercise 1, with the format:=== BiblioTech Catalog === 1) <title 1> 2) <title 2> ...(Hint: you can combine a
forloop withStringBuilder.AppendLineand string interpolation to build each numbered line).
Solutions
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones", "The Aleph", "Pedro Páramo" };
// Iterating with foreach
foreach (string title in bookTitles)
{
Console.WriteLine(title);
}
Console.WriteLine("---");
// Iterating with for, numbered from 1
for (int i = 0; i < bookTitles.Length; i++)
{
Console.WriteLine($"{i + 1}. {bookTitles[i]}");
}
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones", "The Aleph", "Pedro Páramo" };
foreach (string title in bookTitles)
{
if (title.ToLower().Contains("a"))
{
Console.WriteLine(title);
}
}
Every title in the example contains the letter "a" somewhere (uppercase or lowercase), so
the result would show all five; the exercise is meant to practice combining ToLower() and
Contains(), rather than to obtain a smaller subset with this particular data. You can try
it with other titles that don't contain the letter to check that filtering actually works.
using System.Text;
string[] bookTitles = { "One Hundred Years of Solitude", "Hopscotch", "Ficciones", "The Aleph", "Pedro Páramo" };
StringBuilder report = new StringBuilder();
report.AppendLine("=== BiblioTech Catalog ===");
for (int i = 0; i < bookTitles.Length; i++)
{
report.AppendLine($"{i + 1}) {bookTitles[i]}");
}
Console.WriteLine(report.ToString());
Expected result:
=== BiblioTech Catalog ===
1) One Hundred Years of Solitude
2) Hopscotch
3) Ficciones
4) The Aleph
5) Pedro Páramo
Conclusion
In this lesson you learned to work with single- and multi-dimensional arrays, to iterate
over them with foreach and for, and you saw why List<T> (which we'll study in Module 4)
solves the fixed-size limitation of arrays. You also went deeper into the string class: its
immutability, concatenation, $"" interpolation, its most common methods (Split, Trim,
Contains, Substring, ToUpper/ToLower), and the use of StringBuilder to concatenate
text efficiently inside loops.
This closes Module 1: Introduction to C#. You now know how to install and configure your
environment, write and run console programs, correctly apply the language's syntax, declare
variables of different types, and work with arrays and strings — all of it applied, step by
step, to the BiblioTech project. In Module 2: Control Structures you'll learn to make
decisions in your code with conditional statements, to repeat actions with loops, to organize
multiple cases with switch, and to handle errors robustly with exceptions: essential pieces
for BiblioTech to start behaving like a real application, capable of reacting to different
situations and data.
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
