C#
List
Array
Initialization
Programming

Why can I initialize a List like an array in C?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C#, List<T> can be initialized with brace syntax that looks similar to array initialization. This often confuses newcomers because arrays and lists are different types with different memory behavior. The shared syntax comes from language features called collection initializers, not from lists being arrays.

Array Initialization vs List Initialization

Array initialization directly creates a fixed-size array object.

csharp
int[] numbers = { 1, 2, 3, 4 };
Console.WriteLine(numbers.Length); // 4

List initialization uses a constructor call and then applies initializer items.

csharp
1using System.Collections.Generic;
2
3List<int> values = new List<int> { 1, 2, 3, 4 };
4Console.WriteLine(values.Count); // 4

The syntax is similar, but semantics are different.

What the Compiler Does for List<T>

Collection initializer syntax is compiler sugar. The compiler rewrites items into Add calls on the created object.

Conceptual expansion:

csharp
1var values = new List<int>();
2values.Add(1);
3values.Add(2);
4values.Add(3);
5values.Add(4);

So the key requirement is that target type has an accessible Add method and supports enumeration patterns expected by the language.

Why This Feature Exists

C# uses initializer syntax for readability and consistency. Developers can declare and populate collections in one statement, reducing boilerplate and improving intent clarity.

This is especially useful for test data, configuration-like setup, and small in-memory datasets.

Custom Types Can Use the Same Syntax

Your own collection-like type can support initializer syntax by exposing a compatible Add method.

csharp
1using System;
2using System.Collections;
3using System.Collections.Generic;
4
5public class NumberBag : IEnumerable<int>
6{
7    private readonly List<int> _inner = new();
8
9    public void Add(int value) => _inner.Add(value);
10
11    public IEnumerator<int> GetEnumerator() => _inner.GetEnumerator();
12    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
13}
14
15var bag = new NumberBag { 10, 20, 30 };

This shows initializer support is a language-level capability, not a special case only for List<T>.

Performance and Capacity Considerations

Arrays allocate fixed length once. Lists grow dynamically and may reallocate as items are added. If final size is known, pre-sizing list capacity can reduce allocations.

csharp
1var list = new List<int>(capacity: 1000);
2for (int i = 0; i < 1000; i++)
3{
4    list.Add(i);
5}

This can improve performance in tight loops.

When to Choose Array or List

Use arrays when size is fixed and indexed access is all you need. Use lists when size may change, insertion and removal are needed, or you prefer richer collection APIs.

A practical rule:

  • stable size and low-level performance focus: array
  • dynamic size and convenient methods: list

Relation to Object and Collection Initializers

C# also supports object initializers, and both features can be combined in readable setup blocks.

csharp
1var users = new List<User>
2{
3    new User { Name = "Ava", Age = 30 },
4    new User { Name = "Noah", Age = 28 }
5};

Collection initializer invokes Add for each element, while object initializer sets properties on each object. Understanding this composition explains why the syntax feels compact yet remains strongly typed.

Readability Guidelines

Initializer syntax is great for small datasets. For large or dynamic data construction, explicit loops can be clearer and easier to debug. Teams often adopt a style rule: use initializers for static seed data, and use loops for conditional population.

Common Pitfalls

  • Assuming list initializer creates immutable data like some other languages.
  • Forgetting that lists can reallocate and invalidate references to internal storage.
  • Using array when frequent insert and remove operations are required.
  • Confusing compile-time syntax sugar with runtime type behavior.
  • Ignoring list capacity when building large collections in hot paths.

Summary

  • List brace initialization uses collection initializer syntax.
  • Compiler rewrites initializer items into Add calls.
  • Arrays and lists share syntax style but have different runtime behavior.
  • Custom types with Add can also use initializer syntax.
  • Choose array or list based on mutability and workload patterns.

Course illustration
Course illustration

All Rights Reserved.