Updated on

Use List<T>. Microsoft’s own ArrayList reference says it outright: “We don’t recommend that you use the ArrayList class for new development. Instead, we recommend that you use the generic List<T> class.” ArrayList is the non-generic collection from .NET Framework 1.0, kept only so that twenty-year-old code still compiles.

The reason is type safety. ArrayList stores everything as object, so the compiler cannot stop us putting a string into a collection of numbers, and every value we add is boxed onto the heap. List<T> catches the mistake at compile time and boxes nothing.

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

ArrayList and List Overview

ArrayList is a non-generic collection type that allows users to store objects of any data type and it is available under System.Collections namespace. We can learn more about ArrayList from this article: Working With Collections in .NET

On the other way, a List is a generic collection type that allows users to store objects of data type as specified by the List at the time of declaration. It is a very strongly typed collection and is available under System.Collections.Generic namespace. We also have an article on List Collection in C# explaining the concepts in depth.

Both types sit on the same set of collection abstractions, and our guide to how IEnumerable, ICollection, and IList relate covers the interfaces they implement.

What Is an ArrayList in C#?

ArrayList is a non-generic collection in System.Collections that holds items as object and grows automatically as we add to it.

It came with .NET Framework 1.0, before C# had generics. Because it stores object, a single ArrayList can hold a number, a string, and a DateTime at the same time. Nothing stops us doing that by accident.

Reading from it costs a cast, and every value type we put in gets boxed onto the heap on the way in and unboxed again on the way out. That is an allocation and an indirection on every single element.

List<T> replaced it in .NET Framework 2.0 and does the same job with the element type declared up front, which the compiler then enforces for us. New code should use List<T> without exception; ArrayList is still worth recognising, because it turns up in code written before 2005.

Three lines show what that permissiveness costs us:

var list = new ArrayList { 1, "two", DateTime.Now };

var first = (int)list[0];      // cast required
var oops = (int)list[1];       // compiles, throws InvalidCastException

The collection accepts all three values, reading the first one back costs a cast, and the wrong cast on the second one still compiles. If we are maintaining code that already uses the type, we have a closer look at ArrayList and its API in a dedicated article.

ArrayList vs List in C#: What Is the Difference?

The difference is generics. List<T> fixes the element type when we declare it; ArrayList accepts anything and calls it object.

That single choice produces every other difference. Because the compiler knows a List<int> holds integers, it rejects list.Add("4") before the program runs, while the same mistake on an ArrayList compiles happily and fails later as an InvalidCastException: possibly in a different method, possibly in production.

It also produces the performance gap. Every int added to an ArrayList is boxed into a heap object and unboxed again on the way out, so a loop over a thousand numbers allocates a thousand short-lived objects for the collector to clean up. List<int> stores the integers directly, inside a single array.

And it produces the everyday ergonomics. A foreach over a List<T> gives us typed items we can use immediately; over an ArrayList it gives us object, and every use of the item needs a cast first.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

Nature of Collection

By nature, ArrayList holds a heterogeneous collection of objects. This means that in a single instance of ArrayList, we can store objects of any data type. On the other hand, List is designed to hold just a homogeneous collection of objects. This means that in a single instance, we can store objects of only one data type.

Let’s see an example of ArrayList storing different object types:

using System.Collections;

var arrayList = new ArrayList();
arrayList.Add(1); // integer
arrayList.Add(2);
arrayList.Add("3"); // string

And now let’s see an example on List that allows only one kind of object:

using System.Collections.Generic;

var list = new List<int>();
list.Add(1); // allows only integer values
list.Add(2);
list.Add(3);

Error Prone

With ArrayList, we can always expect runtime errors while accessing collections as it stores heterogeneous objects. But List is a strongly typed collection that allows objects of the type defined as T in List<T>. Hence the app will throw a compile-time error if we ever try to store anything other than what was specified for T.

Now, using the same list instance, if we try to add a string, we will face a compile error:

list.Add("4"); // Gives compile error

Boxing/Unboxing Needs

When we use ArrayList, it often requires us to box or unbox the objects that we are accessing to avoid any errors. But with List, it’s never the case.

Let’s see how:

int sum = 0;
foreach (var item in arrayList)
{
    sum += Convert.ToInt32(item);
}
Console.WriteLine($"Sum is {sum}");

The output will be:

Sum is 6

As we can see with ArrayList we had to convert "3" to 3 using Convert.ToInt32() to make the computation work and avoid the runtime error.

But in the case of List, we don’t need to do that:

int sum = 0;
foreach (var item in list)
{
    sum += item;
}
Console.WriteLine($"Sum is {sum}");

And the output will still be the same:

Sum is 6

As we can see the List is type-safe and we didn’t have to do any type-casting at all.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

Memory Management

For value types, List is the more memory-efficient of the two, and boxing is the reason. Every int we add to an ArrayList becomes a separate object on the heap, with its own object header, and the ArrayList stores a reference to it. A List<int> stores the integers themselves, packed into a single array.

For reference-type elements the gap largely closes, because then both collections are storing references either way. The memory argument is really an argument about boxing.

Performance Efficiency

The performance difference comes from the same place: allocations. A loop that adds a thousand integers to an ArrayList also allocates a thousand short-lived boxes for the garbage collector to reclaim, and every read back costs an unboxing cast. A List<int> does neither.

Type safety on its own is not a speed feature, and both collections index into a backing array in much the same way. Where List is faster, it is faster because it does not box, which is why the effect is large for value types and small for reference types.

Usage Preferences

ArrayList can store objects of any data type, but that flexibility is not by itself a reason to pick it. If the application we’re developing targets a .NET Framework version below .NET Framework 2.0, generics do not exist yet and ArrayList is what we have. On .NET Framework 2.0 and everything after it, which is every runtime in support today, List is the choice, for all the reasons we have seen in the previous sections. The same generic-versus-non-generic split turns up elsewhere in the framework, as our comparison of Dictionary versus the non-generic Hashtable shows.

Should We Use ArrayList or List in C#?

Use List<T>. There is no scenario in new code where ArrayList is the better choice.

The only reason to touch ArrayList today is code that already uses it. Even then, swapping the type is not free (every cast, every object variable, and every method signature that accepts it has to change with it), so the sensible move is to convert at the boundary rather than everywhere at once.

If a collection genuinely has to hold unrelated types, ArrayList is still the wrong tool. List<object> expresses the same thing with generic LINQ and generic APIs available, and a common base class or interface expresses it better than either.

For the one case ArrayList was good at (a growable array of mixed primitives), the modern answer is a List<T> of a record or a discriminated shape, so the compiler still knows what came out.

CriterionArrayListList<T>
NamespaceSystem.CollectionsSystem.Collections.Generic
GenericNoYes
Element typeobjectT
Wrong-type element caughtAt runtime, as InvalidCastExceptionAt compile time
Boxing of value typesYes, on every add and readNo
Casting on readRequiredNot required
Mixed types in one instanceAllowedNot allowed
LINQ without a castNo, it needs Cast<T>() or OfType<T>()Yes
Available since.NET Framework 1.0.NET Framework 2.0
Use it whenMaintaining pre-generics codeAlways, by default

That is Microsoft’s own recommendation for the case, not a workaround: “For a heterogeneous collection of objects, use the List<Object> (in C#) or List(Of Object) (in Visual Basic) type.”

Conclusion

In this article, we learned what an ArrayList is, how it differs from List, and which of the two to reach for. The difference comes down to a single decision: List fixes the element type at declaration and ArrayList does not, and everything else, from the compile-time error to the boxing, follows from that. For new code the answer is List, without exception.

The generic collections have kept growing since, and our article on immutable collections covers the ones designed for data that should not change after we build it.

Tested with .NET 10.0.10.