9.2. List
Arrays are fine if you know ahead of time how long your sequence of items is. Then you create your array with that length, and you are all set.
If you want a variable sized container, you are likely to want a List
.
List is a type of collection that is used when the number of elements is unknown.
As with arrays, you might want a collection of any particular type.
Unfortunately, you cannot use the simple notation of arrays to specify
the type of element in a List
. Array syntax is
built into the language. Lists are handled in the library of types
provided by C# from .NET.
.. There are all sorts of situations where you might want
.. a general idea to have a version for each of many kinds of objects.
9.2.1. Generics
Since .NET 4.0, the one new form of syntax that can apply to all sorts of
classes, generics is introduced. There is a namespace for the generics
for collections, including List: System.Collections.Generic
. You used to have
to use the using
directive to import the features offered by the class but
C# now does it automatically. In general, the new generic syntax allows a type in angle
brackets after a class (e.g., List) name.
The type for a generic list collection is:
List<T>
To create a list, you specify a type parameter for the type of data the variable will store. For example, to create a list of string type:
List<string> myNames = new List<string>();
Examples of adding elements to a list:
myNames.Add("Alice");
myNames.Add("Bob");
myNames.Add("Charles");
To loop through a list, you may use the foreach loop:
foreach (string name in myNames)
{
Console.WriteLine(name);
}
Similarly, to create a list of integer type:
List<int> primeNumbers = new List<int>();
primeNumbers.Add(2); // adding elements using add() method
primeNumbers.Add(3);
primeNumbers.Add(5);
primeNumbers.Add(7);
9.2.2. List Constructors and Methods
We can play with some List
methods in csharprepl. Note that csharprepl
informally displays the values of a List
with Name and Type in a table like:
> words
List<string>(1)
┌──────┬─────────┬────────┐
│ Name │ Value │ Type │
├──────┼─────────┼────────┤
│ [0] │ "Apple" │ string │
└──────┴─────────┴────────┘
The blocks below are all from one csharprepl session, with our comments
breaking up the sequence. With the no-parameter constructor
(the parentheses at the
end of the variable declaration), the List
is empty to start:
> List<string> words = new List<string>();
> words;
List<string>(0)
> words.Count
0
You can add elements, and keep count with the Count
property
as the size changes:
> words.Add("Apple");
> words
List<string>(1)
┌──────┬─────────┬────────┐
│ Name │ Value │ Type │
├──────┼─────────┼────────┤
│ [0] │ "Apple" │ string │
└──────┴─────────┴────────┘
> words.Add("Banana");
> words
List<string>(2)
┌──────┬──────────┬────────┐
│ Name │ Value │ Type │
├──────┼──────────┼────────┤
│ [0] │ "Apple" │ string │
│ [1] │ "Banana" │ string │
└──────┴──────────┴────────┘
> words.Add("Cherry");
> words
List<string>(3)
┌──────┬──────────┬────────┐
│ Name │ Value │ Type │
├──────┼──────────┼────────┤
│ [0] │ "Apple" │ string │
│ [1] │ "Banana" │ string │
│ [2] │ "Cherry" │ string │
└──────┴──────────┴────────┘
> words.Count;
┌───────────────────────────────────────────CompilationErrorException────────────────────────────────────────────┐
│ (1,1): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be │
│ used as a statement │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
> words.Count
3
You can reference and change elements by index, like with arrays
:
> words[0];
"Apple"
> words[2];
"Cherry"
> words[2] = "Coconut";
> words;
List<string>(3)
┌──────┬───────────┬────────┐
│ Name │ Value │ Type │
├──────┼───────────┼────────┤
│ [0] │ "Apple" │ string │
│ [1] │ "Banana" │ string │
│ [2] │ "Coconut" │ string │
└──────┴───────────┴────────┘
You can use foreach
like with arrays or other sequences:
> foreach (string s in words)
{
Console.WriteLine(s.ToUpper());
}
APPLE
BANANA
COCONUT
Note: Unfortunately C# is not user-friendly if
you try to use Console.WriteLine
to print a List
object:
> Console.WriteLine(words)
System.Collections.Generic.List`1[System.Int32]
Next, compare Remove
, which finds the first matching element and removes it,
and RemoveAt
, which removes the element at a specified index.
Remove
returns whether the List has been changed:
> words.Remove("Apple");
true
> words
List<string>(2)
┌──────┬───────────┬────────┐
│ Name │ Value │ Type │
├──────┼───────────┼────────┤
│ [0] │ "Banana" │ string │
│ [1] │ "Coconut" │ string │
└──────┴───────────┴────────┘
>
> words.Add("Avocado");
> words.Add("Durian");
> words
List<string>(4)
┌──────┬───────────┬────────┐
│ Name │ Value │ Type │
├──────┼───────────┼────────┤
│ [0] │ "Banana" │ string │
│ [1] │ "Coconut" │ string │
│ [2] │ "Avocado" │ string │
│ [3] │ "Durian" │ string │
└──────┴───────────┴────────┘
> words.RemoveAt(3)
> words
List<string>(3)
┌──────┬───────────┬────────┐
│ Name │ Value │ Type │
├──────┼───────────┼────────┤
│ [0] │ "Banana" │ string │
│ [1] │ "Coconut" │ string │
│ [2] │ "Avocado" │ string │
└──────┴───────────┴────────┘
Removing does not leave a “hole” in the List
: The list closes up,
so the index decreases for the elements after the removed one:
> words.Count;
3
You can check for membership in a List
with Contains
:
> words.Contains("Apple")
false
> words.Contains("Banana")
true
>
You can also remove all elements at once:
> words.Clear()
> words
List<string>(0)
>
Here is a List containing int
elements.
Though more verbose than for an array, you can initialize a List
with another collection, including an anonymous array
,
specified with an explicit sequence in curly braces:
> List<int> nums = new List<int>(new[] { 1, 2, 3, 4, 5 });
> nums
List<int>(5)
┌──────┬───────┬──────┐
│ Name │ Value │ Type │
├──────┼───────┼──────┤
│ [0] │ 1 │ int │
│ [1] │ 2 │ int │
│ [2] │ 3 │ int │
│ [3] │ 4 │ int │
│ [4] │ 5 │ int │
└──────┴───────┴──────┘
We have been using the explicit declaration syntax, but generic types tend to get long,
so the keyword var
for implicitly-typed variable creation is handy with them:
var stuff = new List<string>();
When initializing a generic object, you still need to remember both the angle braces around the type and the parentheses for the parameter list after that. Or, you can try initializing a collection object with collection initialization:
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };
9.2.3. Interactive List Example
As in contrast to array
, lists
are handy when you do not know how much
data there will be. A simple example would be reading in lines from the user
interactively:
/// Return a List of lines entered by the user in response
/// to the prompt. Lines in the List will be nonempty, since an
/// empty line terminates the input.
List<string> ReadLines(string prompt)
{
List<string> lines = new List<string>();
Console.WriteLine(prompt);
Console.WriteLine("An empty line terminates input.");
string line = Console.ReadLine();
while (line.Length > 0) {
lines.Add(line);
line = Console.ReadLine();
}
return lines;
}