Inline Instantiation of Constant Lists in C#: An In-Depth Analysis of const vs. readonly

Dec 07, 2025 · Programming · 6 views · 7.8

Keywords: C# | Constant List | Immutable Collection | ReadOnlyCollection | static readonly

Abstract: This paper explores how to correctly implement inline instantiation of constant lists in C# programming. By analyzing the limitations of the const keyword for reference types, it explains why List<string> cannot be directly declared as a const field. The article focuses on solutions using static readonly combined with ReadOnlyCollection<T>, detailing comparisons between different declaration approaches such as IList<string>, IEnumerable<string>, and ReadOnlyCollection<string>, and emphasizes the importance of collection immutability. Additionally, it provides naming convention recommendations and code examples to help developers avoid common pitfalls and write more robust code.

Problem Background and Error Analysis

In C# development, developers often need to define a set of constant string lists, such as for storing metric names. An intuitive attempt is to use the const keyword for inline instantiation, as shown below:

public const List<String> METRICS = new List<String>()
{
    SourceFile.LOC,
    SourceFile.MCCABE,
    // Other elements...
};

However, this code causes a compilation error: "FileStorer.METRICS' is of type 'System.Collections.Generic.List<string>'. A const field of a reference type other than string can only be initialized with null.". The core issue lies in the semantic limitations of the const keyword. const is used to declare compile-time constants, whose values must be fully determined at compile time. For reference types (except string), due to the dynamic nature of runtime object instantiation, their memory addresses cannot be fixed at compile time, so they can only be initialized to null. The string type is an exception because the C# compiler treats string literals specially as compile-time constants.

Solution: static readonly and Immutable Collections

To solve this problem, it is recommended to use static readonly instead of const. readonly fields allow one-time assignment at declaration or in the constructor, suitable for constant values determined at runtime. A basic implementation is as follows:

public static readonly List<String> Metrics = new List<String>
{
    SourceFile.LoC,
    SourceFile.McCabe,
    SourceFile.NoM,
    SourceFile.NoA,
    SourceFile.FanOut,
    SourceFile.FanIn,
    SourceFile.Par,
    SourceFile.Ndc,
    SourceFile.Calls
};

But this approach has a potential issue: while the Metrics field itself is read-only (i.e., cannot be reassigned), the referenced List<String> object remains mutable. Other code can call Metrics.Add("unwanted item") to modify the collection content, which may violate design intent. To ensure complete immutability of the collection, it should be wrapped with ReadOnlyCollection<T>.

Implementing Strong Immutability with ReadOnlyCollection<T>

ReadOnlyCollection<T> is a wrapper class in the System.Collections.ObjectModel namespace that encapsulates an underlying collection (such as List<T>) and exposes a read-only interface. Example code:

public static readonly IList<String> Metrics = new ReadOnlyCollection<string>
    (new List<String> { 
        SourceFile.LoC, SourceFile.McCabe, SourceFile.NoM,
        SourceFile.NoA, SourceFile.FanOut, SourceFile.FanIn, 
        SourceFile.Par, SourceFile.Ndc, SourceFile.Calls });

Here, ReadOnlyCollection<string> takes a List<String> as a parameter. Since external code cannot access the original list, the collection content cannot be modified. This design pattern is highly effective when exposing immutable data collections, such as configuration lists or enumeration value sets.

Interface Selection and Design Considerations

When declaring immutable collections, choosing the appropriate interface type is crucial, as it affects code usability and maintainability:

In practical projects, it is advisable to weigh choices based on usage scenarios. For example, IEnumerable<string> may suffice for internal utility classes, while ReadOnlyCollection<string> can more clearly convey immutability constraints in public APIs.

Naming Conventions and Best Practices

Following .NET naming conventions helps improve code consistency. According to Microsoft guidelines, public static fields should use PascalCase naming, e.g., changing METRICS to Metrics. Additionally, using full descriptive names and avoiding abbreviations enhances code readability. For instance, SourceFile.LoC could be renamed to SourceFile.LinesOfCode to make its meaning clearer.

Extended Discussion and Alternatives

Beyond ReadOnlyCollection<T>, C# offers other immutable collection types, such as ImmutableArray<T> and ImmutableList<T> (from the System.Collections.Immutable namespace). These types offer further advantages in performance optimization and thread safety, suitable for high-concurrency scenarios. For example:

public static readonly ImmutableArray<String> Metrics = ImmutableArray.Create(
    SourceFile.LinesOfCode, SourceFile.McCabe, // Other elements...
);

However, for most applications, ReadOnlyCollection<T> is sufficient and requires no additional dependencies. Developers should choose the simplest solution based on project needs.

Conclusion

When implementing inline instantiation of constant lists in C#, avoid using the const keyword and instead adopt a strategy of static readonly combined with immutable collections. By wrapping the underlying list with ReadOnlyCollection<T>, complete collection immutability can be ensured, while selecting appropriate interface types (such as IEnumerable<string> or IList<string>) balances functionality and constraints. Following naming conventions and considering alternatives (like immutable collection libraries) further enhances code quality. This approach not only resolves compilation errors but also improves code robustness and maintainability, making it a recommended practice for handling static constant collections.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.