Do Not Expose Generic Lists

ID

csharp.do_not_expose_generic_lists

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Best Practice

Language

CSharp

Tags

api_design, best_practice

Description

Reports public or protected API members whose declared type is System.Collections.Generic.List<T>. The .NET design guidelines recommend exposing an interface instead, so callers depend on the contract rather than the specific implementation.

Rationale

Returning a concrete List<T> leaks the implementation. Callers can mutate the collection, the implementation cannot later swap to a different backing structure (immutable list, array segment, lazy view) without a breaking change, and read-only intent cannot be communicated. IList<T>, ICollection<T> and IReadOnlyList<T> model the relevant contracts directly.

Only members that are actually reachable from outside the declaring assembly are reported: a public/protected member nested inside a private or internal type is not on the external API surface (nothing outside the enclosing type can see it), and private protected (visible only to derived types in the same assembly) does not carry the cross-assembly binary-compatibility risk this rule targets either.

public class Inventory
{
    public List<string> Items { get; }                            // FLAW

    public List<string> Fetch(int n) => new();                    // FLAW

    public IReadOnlyList<string> History { get; } = new List<string>(); // OK
    public IList<string> Categories { get; }                      // OK

    public IList<string> Search(string term) => new List<string>(); // OK
}

Remediation

Change the declared return type or property type to the most appropriate collection interface — IReadOnlyList<T> for read-only consumers, IList<T> or ICollection<T> when mutation is part of the contract.