1. Array of type string
So, real quick, let's talk about arrays of type string.
Like we already said, an array can be of any type. Which means you can totally create an array of type string. Here’s what the code would look like if we needed to write a program that "reads 10 strings from the keyboard and prints them in reverse order."
string[] array = new string[10]; // Create an array object with 10 elements
for (int i = 0; i < 10; i++) // Loop from 0 to 9
{
array[i] = Console.ReadLine(); // Read a string from the keyboard and save it in the array cell
}
for (int i = 9; i >= 0; i--) // Loop from 9 to 0
{
Console.WriteLine(array[i]); // Print the current array cell to the screen
}
The code barely changed! The only thing we had to do was swap the int type for string when creating the array. Everything else is exactly the same!
2. string array in memory
And here’s another useful fact. Check out these 3 pics:
Picture 1. How a string object is stored in memory:
Notice that the text of the string isn’t stored right in the variable: it gets its own separate memory block. And the string variable just stores the address (reference) to the object with the text.
Picture 2. How an int array is stored in memory:
You’ve seen this pic before too.
Picture 3. How a string array is stored in memory:
On the left, we see a variable-array of type string[] (it stores the address of the array object).
In the middle — the array object of type string.
And on the right — string objects that store some texts.
The cells of the array object of type string don’t store the strings (texts) themselves, but their addresses (references). Just like variables of type string store the addresses of strings (text).
3. Quick array initialization in C#
Arrays are super useful, so the C# devs tried to make working with them as convenient as possible. The first thing they did was make array initialization easier, so you can set up starting values right away.
Because a lot of the time, besides data your program reads from somewhere, it also needs its own internal data. For example, let’s say we need to store the lengths of all the months in an array. Here’s what that code might look like:
int[] months = new int[12];
months[0] = 31; // January
months[1] = 28; // February
months[2] = 31; // March
months[3] = 30; // April
months[4] = 31; // May
months[5] = 30; // June
months[6] = 31; // July
months[7] = 31; // August
months[8] = 30; // September
months[9] = 31; // October
months[10] = 30; // November
months[11] = 31; // December
But there’s a shorter way to write this — thanks to the C# creators:
// month lengths of the year
int[] months = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
You can just list all the array values separated by commas!
Pretty handy, right? But wait, there’s more.
Turns out the compiler can figure out the container type (the array object) based on the array variable type. And to figure out the array length — it just counts the number of elements in the curly braces.
So you can write the code even shorter:
// month lengths of the year
int[] months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
Isn’t that sweet? 🙂
This is called "quick array initialization." And by the way, it works not just for int...
// month names of the year
string[] months = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
4. The foreach loop
Looping through all the elements of an array is such a common thing that there’s a special loop for it(!). It’s called foreach. Here’s how it looks:
foreach (int score in scores)
{
Console.WriteLine($"Points: {score}");
}
Here, the loop itself goes through the elements — no manual index needed. You can’t change the elements directly (they’re read-only in the loop), but it’s super convenient for just walking through the array.
The compiler will turn the foreach loop code into this:
for (int i = 0; i < scores.Length; i++)
{
int score = scores[i];
Console.WriteLine($"Points: {score}");
}
So, no magic here. Now it’s clear why you can’t change array elements through the score variable.
Let’s write another example: let’s find the sum of all the points.
int sum = 0;
foreach (int score in scores)
{
sum += score;
}
Console.WriteLine($"Sum of all points: {sum}");
When should you use which loop?
- If you need the index or want to change elements — use a for loop.
- If you just want to walk through the values — foreach is faster and cleaner.
5. End-indexing: the ^ operator
For a while now in C#, you can get elements from the end of an array. Back in the day, if you wanted the last element, you’d write something like:
int lastScore = scores[scores.Length - 1];
Now it’s way easier. Just put the ^ operator before the index, and it counts from the end:
int lastScore = scores[^1]; // Last element
int penultimate = scores[^2]; // Second-to-last element
- ^1 — that’s the first from the end (the last one).
- ^2 — second from the end.
Visualization
| Index | Value in scores array | Regular index | End index |
|---|---|---|---|
| 0 | 10 | 0 | ^5 |
| 1 | 15 | 1 | ^4 |
| 2 | 8 | 2 | ^3 |
| 3 | 22 | 3 | ^2 |
| 4 | 17 | 4 | ^1 |
score[^1] = 17, score[^2] = 22 — easy and clear.
Important! Notice that end indexes start from 1, not zero.
6. Ranges: the .. operator
Sometimes you need to get a subarray from the original array. In C#, you can use the special range operator .. — it’s a short and super handy way to get the "slice" you want.
This subarray is sometimes called a slice, and it’s defined by two boundaries: start and end.
Heads up: the right boundary is NOT included in the result!
What it looks like
int[] scores = { 10, 15, 8, 22, 17 };
int[] top3 = scores[0..3]; // Take elements from 0 to 2: {10, 15, 8}
- scores[0..3] — we take elements from index 0 (inclusive) up to index 3 (NOT inclusive).
- The resulting array will have elements with indexes 0, 1, and 2.
Shortcuts
If the left boundary isn’t specified, you start from the very beginning:
int[] firstTwo = scores[..2]; // same as scores[0..2], so {10, 15}
If the right boundary isn’t specified, you go all the way to the end:
int[] fromThird = scores[2..]; // {8, 22, 17}
Negative indexes
With the ^ symbol, you can count indexes from the end:
int[] lastTwo = scores[^2..]; // {22, 17}
Here, ^2 means "second element from the end."
You can also take a range "from and to" using negative indexes on both sides:
int[] mid = scores[1..^1]; // {15, 8, 22} (from the 2nd to the penultimate)
What you need to remember
- The right edge is always not included — so if you write a..b, you get all elements from a to b-1.
- If you set boundaries that go outside the array, you’ll get an IndexOutOfRangeException.
- A slice is a new array, the original doesn’t change. Any changes in the new array won’t affect the original.
Practical examples
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
int[] start = arr[..3]; // {1, 2, 3}
int[] end = arr[^2..]; // {6, 7}
int[] middle = arr[2..5]; // {3, 4, 5}
int[] all = arr[..]; // {1, 2, 3, 4, 5, 6, 7} (full copy)
GO TO FULL VERSION