In C# 12 there's the "Collection initialization can be simplified" analyser IDE0028.
I've been wondering how the fixer decides when to fix or not fix initialisers such as new(), new List<string>(), new Collection<int>(), Array.Empty<double>(), new string[] { }, etc. It's fix is to replace a qualifying initialiser with [].
For example:
// Collection initialization can be simplified
public List<string> Foo1 { get; } = new List<string>();
public HashSet<string> Foo2 { get; } = new HashSet<string>();
public ICollection<string> Foo3 { get; } = new List<string>();
public Dictionary<int, string> Foo4 = new Dictionary<int, string>();
// Collection initialization can be simplified (may change semantics)
public IEnumerable<string> Bar1 { get; } = new List<string>();
public ICollection<string> Bar2 { get; } = new Collection<string>();
// no code fix offered
public IDictionary<int, string> Baz1 = new Dictionary<int, string>();
I usually choose a collection type that suits a specific purpose. So I don't want to blindly accept the analyser's recommendation without understanding what collection type the compiler will actually use when it sees [].
So:
[]"May change semantics" just means that using a collection literal here does not produce code equivalent to the code you currently have.
Rules for determining what type to use are specified in the Construction and Collection Literal Translation sections of the feature specification.
For concrete classes and structs (without a create method), the collection literal will create an instance of that class/struct using its parameterless constructor, and then add the elements to.
How things are added to the collection also follows some rules, but they are not relevant here because there are no elements in any of the collection literals. Read the spec if you are interested.
This explains why Foo1, Foo2 and Foo4 cases do not change semantics.
For interfaces, the spec gives relatively more freedom. For a read-only interface like IEnumerable<T> or IReadOnlyList<T>, the compiler is free to use any concrete type that fulfils some some requirements.
Therefore, the Bar1 case says "might change semantics". The collection literal is actually translated to Array.Empty() under the hood.
For mutable interfaces like ICollection<T>, it is specified that the collection literal will always be a List<T>, so the Foo3 case does not say "may change semantics", but Bar2 does.
Lastly, collection literals simply does not support IDictionary<K, V>, so there is no diagnostic for Baz1. Out of all the interfaces, collection literals only work with IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T> and IList<T>.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With