C# List Indexing: Access, Update, and Manage Elements

Alexey Karimov

Ever tried to pull a specific value from a list in your Unity script but weren’t sure how? Whether you’re managing an inventory, tracking player scores, or simply storing object references, you’ll often need to grab or update a value at a certain position. 

When that happens, you need to know the right way to handle C# list get item at index operations without breaking your code. Let’s explore multiple ways to access, update, or safely handle list elements using their index.

free trial banner

Accessing Items by Index

When working with lists in Unity C#, the most direct way to pull or update a value is by using its index. Lists in C# are zero-based, meaning the first item is at index 0, the second at 1, and so on. This makes it straightforward to access specific elements, but you need to ensure the index exists to avoid errors.

Here’s a simple example:

using System;
using System.Collections.Generic;

public class IndexExample
{
    public static void Main()
    {
        // Create a list of player names
        List<string> players = new List<string> { "Alex", "Jordan", "Casey", "Riley" };

        // Get the first player
        string firstPlayer = players[0];

        // Update the second player's name
        players[1] = "Taylor";

        // Display results
        Console.WriteLine("First player: " + firstPlayer);
        Console.WriteLine("Updated second player: " + players[1]);
    }
}

This script creates a simple list of player names. It then retrieves the first element using players[0] and updates the second element at index 1. When you run it, you’ll see the first name printed as Alex, and the second updated to Taylor.

Note: Accessing an index that doesn’t exist will throw an ArgumentOutOfRangeException, so always validate the index before using it.

Using LINQ’s ElementAt for Safer Access

Sometimes you want to keep a LINQ flow or avoid an indexer throwing an exception. ElementAt reads an item at a position, and ElementAtOrDefault gives you a default value instead of crashing when the index is out of range.

using System;
using System.Collections.Generic;
using System.Linq;

public class ElementAtExample
{
    public static void Main()
    {
        List<int> scores = new List<int> { 10, 20, 30 };

        // Read with ElementAt
        int first = scores.ElementAt(0);

        // Safe read with fallback (returns 0 if missing for int)
        int maybeFourth = scores.ElementAtOrDefault(3);

        Console.WriteLine("First score: " + first);          // 10
        Console.WriteLine("Fourth score (safe): " + maybeFourth); // 0
    }
}

ElementAt(0) returns the first item. ElementAtOrDefault(3) tries to read index 3, which does not exist, and returns the default value for int (0). For reference types, the default is null. Use ElementAt when you are sure the index is valid. Use ElementAtOrDefault when the index might be missing and you want a safe fallback.

Pro Tip: LINQ methods like ElementAt are great for readability, but they add some overhead, so prefer direct indexing in performance-critical sections.

Access Items from the End

C# introduced the ^ (index-from-end) operator, which makes it easier to grab elements starting from the last item without calculating Count – 1 manually. This is especially useful for quick lookups like retrieving the last or second-to-last element.

using System;
using System.Collections.Generic;

public class IndexFromEndExample
{
    public static void Main()
    {
        List<string> levels = new List<string> { "Easy", "Medium", "Hard", "Expert" };

        // Get the last item
        string lastLevel = levels[^1];

        // Get the second last item
        string secondLastLevel = levels[^2];

        Console.WriteLine("Last level: " + lastLevel);        // Expert
        Console.WriteLine("Second last level: " + secondLastLevel); // Hard
    }
}

The ^ operator lets you count from the end of the list without extra code. ^1 means “last element,” ^2 means “second to last,” and so on. This approach keeps your code cleaner and avoids off-by-one errors that often happen when manually subtracting from Count.

Access a Range of Items with GetRange

Sometimes you need more than a single item, maybe the first three levels of a game or a middle segment of a list. The GetRange method allows you to extract a subset of items by specifying a starting index and a count of how many items to grab.

using System;
using System.Collections.Generic;

public class GetRangeExample
{
    public static void Main()
    {
        List<int> scores = new List<int> { 10, 20, 30, 40, 50, 60 };

        // Get the first three scores
        List<int> topScores = scores.GetRange(0, 3);

        // Get two scores starting from index 3
        List<int> midScores = scores.GetRange(3, 2);

        Console.WriteLine("Top scores: " + string.Join(", ", topScores));
        Console.WriteLine("Mid scores: " + string.Join(", ", midScores));
    }
}

GetRange(startIndex, count) slices the list to create a new list containing just that segment. In the example:

  • GetRange(0, 3) fetches the first three items: 10, 20, 30.
  • GetRange(3, 2) fetches the fourth and fifth items: 40, 50.

Keep in mind that the original list is unchanged, and trying to access a range outside of the list size will throw an ArgumentException, so always validate your indices first.

Pro Tip: Always validate your starting index and range count before calling GetRange, or handle potential exceptions with a try-catch to keep your code stable.

Wrapping Up

Working with list indices in C# is simple but requires careful handling to avoid unexpected errors like out-of-range exceptions or unintended modifications. As a best practice, always validate your indices before accessing elements, especially in dynamic or user-driven scenarios. 

For loops or frequent lookups, using list[index] is faster and avoids the extra allocations or overhead of LINQ-based methods. Use Count checks to stay within bounds, and favor GetRange or LINQ methods when you need safer, more expressive operations. 

Writing small utility methods to handle edge cases can also make your codebase more robust and easier to maintain, especially as projects scale.