In this article, we’ll look at how to update an item in a List in C#, using a simple library management system as an example. We’ll explore the intricacies of this common operation and learn how to manipulate lists in our C# applications effectively.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Understand List in C#

In the world of C#, one of the most commonly used data structures is List<T>. This versatile collection class provides the ability to store and manipulate a series of elements of a specific type, labeled T. It’s similar to an array but with more features. It dynamically adjusts its size as we add or remove elements, and it has several useful methods for sorting, searching, and editing data.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
To gain a better understanding of the List class, please check out our article C# Intermediate – Generic List and Dictionary.

List Set up for Finding and Updating Item

There are several methods for finding elements in a List, each with its own strengths and ideal use cases. We will set up a small library program and go through the different methods.

Let’s start creating a book model:

public class Book(string title, string author, string isbn, bool isCheckedOut)
{
    public string Title { get; set; } = title;

    public string Author { get; set; } = author;

    public string ISBN { get; set; } = isbn;

    public bool IsCheckedOut { get; set; } = isCheckedOut;
}

Let’s continue creating the library class:

public class Library
{
    public List<Book> Books { get; set; }

    public Library()
    {
        Books =
        [
            new("Harry Potter and the Philosopher's Stone", "J.K. Rowling", "978-0439708180", false),
            new("Harry Potter and the Chamber of Secrets", "J.K. Rowling", "978-0439064873", false),
            new("Harry Potter and the Goblet of Fire", "J.K. Rowling", "978-0439139601", false),
            new("Harry Potter and the Order of the Phoenix", "J.K. Rowling", "978-0439358071", false),
            new("Harry Potter and the Half-Blood Prince", "J.K. Rowling", "978-0439785969", false),
        ];
    }

    public Book AddBook(string title, string author, string isbn, bool isCheckedOut)
    {
        var book = new Book(title, author, isbn, isCheckedOut);
        Books.Add(book);

        return book;
    }
}

In our Library class, there are two main parts. First, our constructor seeds the publicly accessible list Books with 5 newly created book objects. Secondly, our AddBook() method creates a new book object using the specified parameter values for the author, title,  ISBN, and checkout status. The newly created book is then added to our Books list.

Find and Update an Item in a List Using Built-In Methods

By default, C# offers methods to update items in a list. Let’s have a look at these methods.

Update List Item With IndexOf() Method

The IndexOf() method is simple and efficient. It returns the zero-based index of the first occurrence of an element in the list. However, it uses the standard equality comparator, which we have to override to match our book comparison case.

Let’s use an index first:

public void CheckoutBookUsingIndexOf(Book book)
{
    var index = Books.IndexOf(book);
    if (index > -1)
    {
        Books[index].IsCheckedOut = true;
    }
}

The IndexOf() method we call in our CheckoutBookUsingIndexOf() method, uses our provided book object to check the Books list from our Library class. In return, we receive the index of the book object in the Books list. Then, we check whether the index is in a valid range and specify the object via the index of the book object in the Books list and change its boolean field IsCheckedOut to true.

As already announced, we still have to make changes to our book class for the IndexOf() method so that we get the right book back. Internally, the IndexOf() method calls the equality comparator Equals() to compare the books. By default, Equals() checks reference equality for reference types such as classes, i.e. it checks whether two object references point to the same instance in memory, not whether their content is the same.

In our case, two Book objects are not considered the same if they are the same object in memory, but if their Title, Author and ISBN properties are the same (essentially two Book objects representing the same actual book).

So let’s update our Book class:

public class Book(string title, string author, string isbn, bool isCheckedOut)
{
    public string Title { get; set; } = title;

    public string Author { get; set; } = author;

    public string ISBN { get; set; } = isbn;

    public bool IsCheckedOut { get; set; } = isCheckedOut;

    public override bool Equals(object? obj)
    {
        if (obj is Book b)
        {
            return this.Title == b.Title && this.Author == b.Author && this.ISBN == b.ISBN;
        }
        else
        {
            return false;
        }
    }
}

Here, we override the Equals() method to change its behavior so that it compares objects based on their properties (title, author, and ISBN) rather than their reference. This way, when IndexOf() calls Equals() internally, it behaves as we intend and considers two Book objects with identical Title, Author and ISBN to be the same, even if they are different instances.

Use the Find() Method to Find And Update a List Item

The Find() method is more flexible. It takes a predicate (a function that returns true or false) and returns the first element that fulfills the condition. This is useful when we need to find an element based on a complex condition.

Let’s create a checkout method using the Find() method:

public void CheckoutBookUsingFind(string isbn)
{
    var bookToCheckout = Books.Find(b => b.ISBN == isbn);
    if (bookToCheckout is not null)
    {
        bookToCheckout.IsCheckedOut = true;
    }
}

In our CheckoutBookUsingFind() method we call the Find() method on our Books list. As a parameter, the Find() method accepts a lambda expression which returns us a book b and we check whether the ISBN of b is equal to isbn.  If not, the return value is null. We store the return value in a new Book object bookToCheckout and check if bookToCheckout is null. If the Find() method returns an object, we then change the IsCheckedOut property of it to true.

List Item Update Using FindIndex() Method

Similarly, FindIndex() returns the index of the first element that fulfills a specific predicate. This is useful when we need the position of an element in the list, not just the element itself.

Let’s create a method similar to our previous examples, but now using the FindIndex() method:

public void CheckoutBookUsingFindIndex(string isbn)
{
    var index = Books.FindIndex(b => b.ISBN == isbn);
    if (index != -1)
    {
        Books[index].IsCheckedOut = true;
    }
}

In our CheckoutBookUsingFindIndex() method, we call the FindIndex() method on the Books list. We use the same condition as we used in our previously created CheckoutBookUsingFind() method. Instead of returning the matching object for the condition, the FindIndex() method returns the index of the matching object in our Books list. We then check the return value. If it is not negative, we check out the book using the index of it on our Books list.

Use LINQ to Find and Update a List Item

In C#, we can also use the Language Integrated Query (LINQ) to find and update an element in a list. This approach is more flexible and more efficient than conventional methods.

Use LINQ FirstOrDefault() Method

LINQ methods such as FirstOrDefault() offer even more flexibility. This method returns the first element that fulfills a condition, or the default value for the type if no such element is found. This is a safe option if we are unsure whether an element is present in the list:

public void CheckoutBookUsingFirstOrDefault(string isbn)
{
    var bookToCheckout = Books.FirstOrDefault(b => b.ISBN == isbn);
    if (bookToCheckout is not null)
    {
        bookToCheckout.IsCheckedOut = true;
    }
}

SingleOrDefault() Method To Update Item

The SingleOrDefault() method works similarly to the FirstOrDefault() method but is a bit stricter. If an item matches the given condition the item does get returned. But unlike with FirstOrDefault() method, an exception is thrown if more than one item matches the condition:

public void CheckoutBookUsingSingleOrDefault(string isbn)
{
    var bookToCheckout = Books.SingleOrDefault(b => b.ISBN == isbn);
    if (bookToCheckout is not null)
    {
        bookToCheckout.IsCheckedOut = true;
    }
}

Find and Update an Item in a List Using ForEach Loop

The most basic approach is to iterate the Books list using a foreach loop:

public void CheckoutBookUsingForeach(string isbn)
{
    foreach (var book in Books)
    {
        if (book.ISBN == isbn)
        {
            book.IsCheckedOut = true;
        }
    }
}

Performance Comparision of Find and Update List Item Methods

Now that we have learned about different methods for updating an element in a list, let’s evaluate which method is the most performant. For performance benchmarking and comparison, we use the BechmarkDotNet framework. We call each created method and have the first entry from our books list returned.

Let’s take a look at the results:

| Method                           | Mean     | Error    | StdDev   | Median   | Rank |
|--------------------------------- |---------:|---------:|---------:|---------:|-----:|
| CheckoutBookUsingFind            | 17.29 ns | 0.699 ns | 1.949 ns | 16.79 ns |    1 |
| CheckoutBookUsingFindIndex       | 18.07 ns | 1.069 ns | 3.085 ns | 17.30 ns |    1 |
| CheckoutBookUsingForeach         | 21.09 ns | 0.449 ns | 0.518 ns | 20.99 ns |    2 |
| CheckoutBookUsingIndexOf         | 23.16 ns | 0.473 ns | 0.545 ns | 23.06 ns |    3 |
| CheckoutBookUsingFirstOrDefault  | 35.04 ns | 1.964 ns | 5.760 ns | 32.27 ns |    4 |
| CheckoutBookUsingSingleOrDefault | 72.29 ns | 1.435 ns | 1.199 ns | 72.45 ns |    5 |

Here, we see the average time required by the various methods to update an element in a list. The CheckoutBookUsingFind() and the CheckoutBookUsingFindIndex() methods have the shortest average time and are, therefore, the most efficient, followed by the CheckoutBookUsingForeach() method. The least efficient methods are CheckoutBookUsingFirstOrDefault() and CheckoutBookUsingSingleOrDefault() with comparatively longer mean times.

Conclusion

In conclusion, the method to use for updating an item in a List in C# depends heavily on the specific project requirements and environment. Our performance test shows that while CheckoutBookUsingFind() is the most efficient, other methods offer unique advantages. CheckoutBookUsingFirstOrDefault() avoids exceptions when nothing is found, and CheckoutBookUsingForeach() is suitable for processing any element. A deep understanding of the individual methods can therefore help us to design our applications more effectively. The “best” method strikes a balance between project requirements, desired results, and performance.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!