Generic List

Learn about the generic counterpart of the ArrayList class.

Introduction

The ArrayList class solved the issues we had with arrays, but it also introduced new issues. .NET developers addressed the concerns with a set of generic collection classes. This lesson focuses on the List<T> class. Instead of using an object[] type array, like ArrayList does, generic collections use parameter types. In the case of List<T>, the internal array is of type T[]. Generic collections introduce strong typing. The List<int> class can only store int items, and List<string> can only store string objects. There won’t be issues like the ones we had with ArrayList.

Syntax

The List<T> class is instantiated just like any other class:

var listOfIntegers = new List<int>();

Here, we create a list that can only store integers inside. The internal array is of type int[].

Lists we create are initially empty. Empty means that the size of the internal array is zero. When we add the first item, though, a new array of size four is created.

listOfIntegers.Add(17);

Each time this array fills, it doubles in size, and values from the previous array are copied over.

This information could be useful for optimization purposes. If we’re sure that we’ll store more than a specific number of items, then we can set the initial size of the list:

var listOfIntegers = new List<int>(500);

The initial size of this list is500. The List<int> class initializes an int[] array of size 500.

Here are some of the most common operations we can perform with List<T>:

  • We can add an item to the end of the list using the Add() method.

  • We can add items from another collection to our list using the AddRange() method.

  • We can insert an item to a specific position using the Insert() method.

  • We can remove an item using the Remove() method

  • We can delete an item at a specific position using the RemoveAt() method.

Examples

Let’s explore some examples below to clarify these methods.

Add and remove items

C#
using System;
// List<T> resides in this namespace
using System.Collections.Generic;
namespace GenericList
{
class Program
{
static void Main(string[] args)
{
var listOfIntegers = new List<int>();
// We can add items individually
listOfIntegers.Add(57);
listOfIntegers.Add(82);
// We can also add another collection
listOfIntegers.AddRange(new int[] { 24, 12, 56, 23, 78 } );
// Gets the number of items
Console.WriteLine($"There are {listOfIntegers.Count} items in our list.");
// Indexers are present
Console.WriteLine($"5th number is {listOfIntegers[4]}.");
// We can remove items
// Removes the first occurrence of number 3.
bool wasFoundAndRemoved = listOfIntegers.Remove(3);
Console.WriteLine($"There was 3 in the list: {wasFoundAndRemoved}.");
// We can remove an item at a specific index
listOfIntegers.RemoveAt(5);
// We can remove all items that match a condition
// Remember predicates?
// In this case, we are removing numbers less than 30
listOfIntegers.RemoveAll(x => x < 30);
Console.Write($"Items left: {listOfIntegers.Count}.");
}
}
}

Sort and search

We could write our own algorithms to sort and search, but List<T> includes them out of the box:

C#
using System;
using System.Collections.Generic;
namespace GenericList
{
class Program
{
static void Main(string[] args)
{
// Instantiating using an initializer
var numbers = new List<int>()
{
1, 78, 0, -5, 25, 31, 90, 23, 67, 53
};
// Sort in an ascending order
numbers.Sort();
PrintList<int>(numbers);
// Searching for 31
// Returns 0-based index or a negative number if not found
// BinarySearch requires the list to be sorted
var indexOf31 = numbers.BinarySearch(31);
if (indexOf31 >= 0)
{
Console.WriteLine($"31 is located at index {indexOf31}.");
}
else
{
Console.WriteLine("Number was not found.");
}
}
static void PrintList<T>(List<T> list)
{
foreach (var item in list)
{
Console.Write(item.ToString() + " ");
}
Console.WriteLine();
}
}
}

Actual capacity

Earlier, we mentioned that the initial size of a list is zero and then it increases to four. The following code snippet serves as proof:

C#
using System;
using System.Collections.Generic;
namespace GenericList
{
class Program
{
static void Main(string[] args)
{
var numbers = new List<int>();
Console.WriteLine(numbers.Capacity); // Initial capacity
numbers.Add(17);
Console.WriteLine(numbers.Capacity); // Capacity after the increase
}
}
}

Our list’s capacity is four, but we have only one item inside. We could remove the excess to free up memory:

C#
using System;
using System.Collections.Generic;
namespace GenericList
{
class Program
{
static void Main(string[] args)
{
var numbers = new List<int>();
Console.WriteLine(numbers.Capacity);
numbers.Add(17);
Console.WriteLine(numbers.Capacity);
// Trimming excess
numbers.TrimExcess();
Console.WriteLine(numbers.Capacity);
}
}
}

Conclusion

As with any other class in .NET, there are too many methods in List<T> to discuss them all. In most cases, we’ll only use the Add() and RemoveAt() methods. Feel free to consult the documentation online to explore other methods and usage scenarios.