ZetCode

C# object & collection initializers

last modified October 17, 2026

In this article we show how to initialize objects and collections with initializers in C#.

With object initializers, we can assign values to any accessible fields or properties of an object at creation time without having to invoke a constructor followed by lines of assignment statements. With collection initializers, we can fill a collection with elements when it is created.

C# object initializer

In the following example, we use an object initializer.

Program.cs
var u1 = new User { FirstName = "John", LastName = "Doe", Occupation = "gardener" };
var u2 = new User { LastName = "Roe", Occupation = "driver", FirstName = "Roger" };

Console.WriteLine(u1);
Console.WriteLine(u2);

class User
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public string Occupation { get; init; } = "unknown";

    public override string ToString()
    {
        return $"User {FirstName} {LastName} {Occupation}";
    }
}

We create two users utilizing the object initializer syntax.

var u1 = new User { FirstName = "John", LastName = "Doe", Occupation = "gardener" };

The member assignments are placed inside a pair of {} brackets and are separated with a comma character. The object is first created with its parameterless constructor; then the assignments are executed.

var u2 = new User { LastName = "Roe", Occupation = "driver", FirstName = "Roger" };

The order of the members is not relevant.

public required string FirstName { get; init; }
public required string LastName { get; init; }
public string Occupation { get; init; } = "unknown";

The init accessor allows a property to be set only during object initialization; afterwards the object is immutable. The required modifier forces the caller to set the property in the object initializer; leaving it out is a compile-time error. The Occupation property is optional and has a default value.

$ dotnet run
User John Doe gardener
User Roger Roe driver

If we omit a required member, for instance with new User { FirstName = "John" }, the compiler reports the CS9035 error. See the C# required modifier and C# init members articles for more details.

C# object initializer with constructor

An object initializer can follow a constructor call with arguments. It works with classes as well as with records.

Program.cs
var c = new Car("Skoda") { Price = 23000, Color = "red" };
Console.WriteLine(c);

var p = new Person("Jane", "Doe") { Email = "jane@example.com" };
Console.WriteLine(p);

class Car(string name)
{
    public string Name { get; } = name;
    public int Price { get; set; }
    public string? Color { get; set; }

    public override string ToString() => $"{Name}: {Price}, {Color}";
}

record Person(string FirstName, string LastName)
{
    public string? Email { get; init; }
}

The mandatory data are passed to the constructor, the optional ones are set in the initializer.

var p = new Person("Jane", "Doe") { Email = "jane@example.com" };

The Person record has two positional properties and an additional Email property, which we set with the object initializer.

$ dotnet run
Skoda: 23000, red
Person { FirstName = Jane, LastName = Doe, Email = jane@example.com }

C# nested object initializers

Object initializers can be nested. A nested initializer can either assign a new object to a property or set the members of an object that the property already holds.

Program.cs
var order = new Order
{
    Id = 1,
    Customer = new Customer { Name = "John Doe", City = "London" },
    Address = { Street = "Baker Street 221B", Zip = "NW1 6XE" },
    Items = { "pen", "notebook" }
};

Console.WriteLine($"{order.Id} {order.Customer.Name} from {order.Customer.City}");
Console.WriteLine($"{order.Address.Street}, {order.Address.Zip}");
Console.WriteLine(string.Join(", ", order.Items));

class Order
{
    public int Id { get; set; }
    public Customer? Customer { get; set; }
    public Address Address { get; } = new();
    public List<string> Items { get; } = [];
}

class Customer
{
    public string? Name { get; set; }
    public string? City { get; set; }
}

class Address
{
    public string? Street { get; set; }
    public string? Zip { get; set; }
}

We create an order with a customer, an address, and a list of items.

Customer = new Customer { Name = "John Doe", City = "London" },

A new Customer object is created and assigned to the property.

Address = { Street = "Baker Street 221B", Zip = "NW1 6XE" },
Items = { "pen", "notebook" }

Without the new keyword, the initializer sets the members of the existing Address object and adds elements to the existing list. This works even with read-only properties, because the properties themselves are not assigned.

$ dotnet run
1 John Doe from London
Baker Street 221B, NW1 6XE
pen, notebook

C# object initializer with anonymous type

We can create anonymous types with object initializers; this is useful in LINQ expressions.

Program.cs
var u1 = new { FirstName = "John", LastName = "Doe", Occupation = "gardener" };
var u2 = new { FirstName = "Roger", LastName = "Roe", Occupation = "driver" };

Console.WriteLine(u1);
Console.WriteLine(u2);
Console.WriteLine(u1.FirstName);

The example creates two users. The compiler generates a type with read-only properties for them; the type has no name that we could use in our code, so the variables must be declared with var.

$ dotnet run
{ FirstName = John, LastName = Doe, Occupation = gardener }
{ FirstName = Roger, LastName = Roe, Occupation = driver }
John

In the next example, we use an anonymous type in a LINQ query expression.

Program.cs
List<User> users =
[
    new(1, "John", "Doe", "London", "gardener", 860),
    new(2, "Lenny", "Ferguson", "New York", "programmer", 980),
    new(3, "Andrew", "Ramos", "Boston", "shop keeper", 600),
    new(4, "Peter", "Stastny", "Prague", "retired", 500),
    new(5, "Anna", "Fabry", "Bratislava", "accountant", 780),
];

var data = from user in users
           select new { user.FirstName, user.LastName, user.Salary };

foreach (var u in data)
{
    Console.WriteLine(u);
}

record User(int Id, string FirstName, string LastName, string City,
    string Occupation, decimal Salary);

We have a list of users where each user has six properties. In the LINQ query expression, we generate a sequence of anonymous objects, where each item has only a subset of the properties. When a property is initialized from another property, its name is inferred; user.FirstName creates a FirstName property.

$ dotnet run
{ FirstName = John, LastName = Doe, Salary = 860 }
{ FirstName = Lenny, LastName = Ferguson, Salary = 980 }
{ FirstName = Andrew, LastName = Ramos, Salary = 600 }
{ FirstName = Peter, LastName = Stastny, Salary = 500 }
{ FirstName = Anna, LastName = Fabry, Salary = 780 }

C# list initializer

In the following example, we use a list initializer.

Program.cs
var vals = new List<int> { 1, -2, -1, 0, 2, 4, 3, -5 };

var positive = vals.Where(val => val > 0);
Console.WriteLine(string.Join(" ", positive));

var users = new List<User>
{
    new() { FirstName = "John", LastName = "Doe", Occupation = "gardener" },
    new() { FirstName = "Roger", LastName = "Roe", Occupation = "driver" },
};

foreach (var user in users)
{
    Console.WriteLine(user);
}

class User
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public required string Occupation { get; init; }

    public override string ToString() => $"User {FirstName} {LastName} {Occupation}";
}

We create a list of integers and a list of users.

var vals = new List<int> { 1, -2, -1, 0, 2, 4, 3, -5 };

The list is initialized in one go; all the elements are specified within the pair of {} brackets.

var users = new List<User>
{
    new() { FirstName = "John", LastName = "Doe", Occupation = "gardener" },
    new() { FirstName = "Roger", LastName = "Roe", Occupation = "driver" },
};

The elements of the list are created with object initializers. With the target-typed new() expression, we do not need to repeat the type name.

$ dotnet run
1 2 4 3
User John Doe gardener
User Roger Roe driver

C# collection initializer and the Add method

A collection initializer is a shorthand for calling the Add method for each element. It works with any type that implements IEnumerable and has an accessible Add method.

Program.cs
using System.Collections;

var bag = new Bag { "apple", "pear", "plum" };
Console.WriteLine(bag);

class Bag : IEnumerable<string>
{
    private readonly List<string> items = [];

    public void Add(string item)
    {
        Console.WriteLine($"adding {item}");
        items.Add(item);
    }

    public IEnumerator<string> GetEnumerator() => items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public override string ToString() => string.Join(", ", items);
}

Our custom Bag collection can be filled with a collection initializer. The output shows that the compiler calls the Add method for each element.

$ dotnet run
adding apple
adding pear
adding plum
apple, pear, plum

C# collection expressions

Since C# 12, collections can also be created with collection expressions. They use square brackets and work with arrays, lists, spans, and other collection types. The .. spread element inserts all the elements of another collection.

Program.cs
int[] vals = [1, 2, 3];
List<int> more = [4, 5, 6];
HashSet<string> words = ["sky", "blue", "sky"];
Span<char> chars = ['a', 'b', 'c'];

int[] all = [0, .. vals, .. more, 7];

Console.WriteLine(string.Join(" ", all));
Console.WriteLine(string.Join(" ", words));
Console.WriteLine(chars.Length);

List<int> empty = [];
Console.WriteLine(empty.Count);

The same syntax creates collections of different types; the type is given by the target. The hash set keeps only unique values.

$ dotnet run
0 1 2 3 4 5 6 7
sky blue
3
0

See the C# collection expressions article for more details.

C# Dictionary initializers

Dictionary initializers can be specified in two ways.

Program.cs
var domains = new Dictionary<string, string>
{
    { "sk", "Slovakia" },
    { "ru", "Russia" },
    { "de", "Germany" },
    { "no", "Norway" }
};

Console.WriteLine(domains["sk"]);

var days = new Dictionary<string, string>
{
    ["mo"] = "Monday",
    ["tu"] = "Tuesday",
    ["we"] = "Wednesday",
    ["th"] = "Thursday",
    ["fr"] = "Friday",
    ["sa"] = "Saturday",
    ["su"] = "Sunday"
};

Console.WriteLine(days["fr"]);

The example creates two dictionaries.

var domains = new Dictionary<string, string>
{
    { "sk", "Slovakia" },
    { "ru", "Russia" },
    { "de", "Germany" },
    { "no", "Norway" }
};

A new dictionary is created. Between the angle brackets <>, we specify the data types of the keys and values. New pairs of key/value elements are written inside nested {} brackets; each pair is separated by a comma character. This is a collection initializer; each pair is passed to the Add method.

Console.WriteLine(domains["sk"]);

To get a value, we specify the dictionary name followed by square [] brackets. Between the brackets, we specify the key.

var days = new Dictionary<string, string>
{
    ["mo"] = "Monday",
    ["tu"] = "Tuesday",
    ...
};

This is an alternative syntax, called an index initializer; the values are assigned to keys using the indexer.

$ dotnet run
Slovakia
Friday

The two forms differ when a key is repeated. The Add method throws an ArgumentException for a duplicate key, while the indexer silently overwrites the previous value.

Program.cs
var d1 = new Dictionary<string, int>
{
    ["a"] = 1,
    ["a"] = 2
};

Console.WriteLine(d1["a"]);

try
{
    var d2 = new Dictionary<string, int>
    {
        { "a", 1 },
        { "a", 2 }
    };
}
catch (ArgumentException e)
{
    Console.WriteLine(e.Message);
}

The first dictionary contains the last value; creating the second one fails.

$ dotnet run
2
An item with the same key has already been added. Key: a

Source

Object and Collection Initializers

In this article we have worked with object & collection initializers in C#.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.