Back to Blog
C#

C# Dictionary Initialization: Syntax and Tradeoffs

c# dictionary initialization: Learn the main ways to initialize a Dictionary in C#, including collection initializers, constructors, and Add methods, with performance...

C#DictionaryCollection InitializerC# SyntaxKeyValuePair
A visual metaphor for C# dictionary initialization showing key-value pairs being inserted into a structured map.

The collection initializer is the most common way to perform C# dictionary initialization. It lets you specify key-value pairs directly at construction time, making the initial contents explicit and readable. For example:

var config = new Dictionary<string, string> { ["host"] = "localhost", ["port"] = "8080", ["timeout"] = "30" };

This syntax works because Dictionary<TKey, TValue> implements IEnumerable and has an Add method that accepts a KeyValuePair<TKey, TValue> or two arguments. The compiler expands the collection initializer into a series of Add calls, so the runtime behavior is equivalent to creating an empty dictionary and then adding each entry. The indexer syntax (["key"] = value) is also supported and assigns directly to the key's value, which means it can overwrite an existing key without throwing an exception. The two-argument Add form throws if the key already exists. Both are valid inside a collection initializer, but they behave differently on duplicate keys.

Collection Initializer with KeyValuePair Syntax

You can also write the initializer using explicit KeyValuePair objects, though it is less common:

var map = new Dictionary<int, string> { new KeyValuePair<int, string>(1, "one"), new KeyValuePair<int, string>(2, "two") };

This form is useful when you already have KeyValuePair instances from another source, but for static data the indexer or the two-argument form is usually more concise. The compiler still translates each line into an Add call, so there is no runtime difference.

Initializing with the Constructor and Capacity

If you know how many entries the dictionary will hold, you can pass an initial capacity to the constructor:

var lookup = new Dictionary<string, int>(100);

This allocates enough internal storage to hold at least 100 entries before resizing. Without an explicit capacity, the dictionary starts with a small default size and grows as you add elements. Each resize allocates a new internal array and copies existing entries, which costs time and memory. Setting a reasonable initial capacity avoids those resizes when the final size is known or can be estimated. This is especially relevant when you are building a dictionary from a large collection and want to minimize reallocation overhead.

Using the Add Method for Incremental Initialization

When you need to build a dictionary conditionally or in a loop, the Add method is the standard approach:

var errors = new Dictionary<string, string>(); if (input.Name == null) { errors.Add("name", "Name is required."); } if (input.Age < 0) { errors.Add("age", "Age cannot be negative."); }

This pattern is clear because each Add call is a separate statement and can be guarded by a condition. It also allows you to start with an empty dictionary and populate it based on runtime logic. The main caveat is that Add throws an ArgumentException if the key already exists. If you want to overwrite an existing key, use the indexer assignment instead:

errors["name"] = "Updated message";

Initializing from Another Collection or LINQ

You can create a dictionary from an existing collection using LINQ's ToDictionary method. This is useful when you have a sequence of objects and want to project a key and value from each element:

var users = GetUsers(); var userById = users.ToDictionary(u => u.Id, u => u.Name);

ToDictionary returns a Dictionary<TKey, TValue> and throws an ArgumentException if duplicate keys appear. You can also pass a custom equality comparer as a third argument if you need case-insensitive keys or other custom comparison logic. This approach is concise but does not let you set an initial capacity directly; the dictionary grows as items are added, so for very large collections you may want to measure whether the overhead matters.

Performance Considerations for Initialization

Dictionary initialization has a direct impact on runtime performance through memory allocation and hash computations. The collection initializer and the Add method both call the dictionary's internal Insert logic, which computes the hash of each key and stores the entry in the appropriate bucket. The number of buckets is determined by the capacity and the load factor. When the dictionary reaches its load factor threshold, it resizes by creating a new internal array and re-hashing all existing entries. This is an O(n) operation, so repeated resizing during initialization can be costly for large dictionaries.

Setting an initial capacity in the constructor reduces the number of resizes. If you know the approximate number of entries, you can avoid most reallocation overhead. However, over-allocating capacity wastes memory because the internal array is sized to the capacity, not the actual count. The default load factor is 1.0, meaning the dictionary resizes when the count equals the capacity. If you set a capacity much larger than needed, you hold onto unused memory until the dictionary is garbage collected.

Another performance detail is the equality comparer. By default, Dictionary<TKey, TValue> uses EqualityComparer<TKey>.Default, which for strings performs case-sensitive ordinal comparison. If you need case-insensitive keys, you can pass StringComparer.OrdinalIgnoreCase to the constructor. This affects how keys are hashed and compared, and it is set once at initialization. Choosing the right comparer can prevent subtle bugs and improve correctness, but it does not change the initialization cost significantly.

Common Pitfalls and Edge Cases

Duplicate keys are the most frequent mistake in dictionary initialization. The collection initializer with the indexer syntax silently overwrites a previous entry, while the two-argument Add form throws. For example:

var dict = new Dictionary<string, int> { ["a"] = 1, ["a"] = 2 // overwrites, no exception }; var dict2 = new Dictionary<string, int> { { "a", 1 }, { "a", 2 } // throws ArgumentException };

Null keys are not allowed in a Dictionary<TKey, TValue> and will cause an ArgumentNullException when you try to add them. This applies to all initialization approaches. If you need to represent a missing key, consider using a sentinel value or a nullable key type if the key is a reference type? Actually nullable reference types still cannot be null at runtime; the dictionary does not accept null keys. For value types, the key cannot be null anyway. So this is a hard constraint.

Case sensitivity is another common issue. Two keys that differ only by case are considered distinct by default. If your data is case-insensitive, you must pass a comparer at initialization. This is a decision you make once, and it affects all subsequent lookups and inserts.

Choosing the Right Initialization Approach

The choice of initialization syntax depends on the source of the data and the required behavior. Use the collection initializer when you have a fixed set of key-value pairs known at compile time; it is concise and self-documenting. Use the constructor with an initial capacity when you are building a dictionary from a large collection and want to minimize resizing overhead. Use the Add method when entries are added conditionally or in a loop, because it allows you to separate the logic into clear statements. Use ToDictionary when you are projecting from an existing sequence and want a one-line transformation. For all approaches, decide on the equality comparer early and pass it to the constructor if you need custom key comparison. This avoids subtle bugs and ensures consistent behavior throughout the dictionary's lifetime.

c# dictionary initialization: Practical Usage and Code Examp | RYUSLOG DEV