In C# 12, the following statement:
IList<Node> nodes = new List<Node>();
can be replaced with the following:
IList<Node> nodes = [];
The documentation is here: learn.microsoft.com - C# Language Reference - Collection Expressions
I do not particularly care about this feature, but once we enable C# 12 in the shop, people will inevitably discover it and will start using it.
According to the documentation, (and indeed, as it can also easily be shown by experiment,) object expressions can be used to create instances not only for types which abide by a certain contract, but also for the following types:
- System.Collections.Generic.IEnumerable
- System.Collections.Generic.IReadOnlyCollection
- System.Collections.Generic.IReadOnlyList
- System.Collections.Generic.ICollection
- System.Collections.Generic.IList
So, in C# it is in fact possible to do the following:
IEnumerable<int> c1 = [1, 2];
IReadOnlyCollection<int> c2 = [2, 3];
IReadOnlyList<int> c3 = [3, 4];
ICollection<int> c4 = [4, 5];
IList<int> c5 = [5, 6];
In these cases, a very reasonable question to ask is: what types are instantiated here? The documentation doesn't say, so let's take a look with the debugger:
As it turns out, for mutable interfaces, collection expressions create instances of the List<T>. For immutable interfaces, they create instances of some <>z__ReadOnlyArray<T> type, which, as a bit of experimentation reveals, is just as defective as List<T>.
Why are they defective? Because their Equals() method does not actually check whether their contents are equal; instead, it defers to object.Equals(), which simply does a reference comparison. So, you may have two instances of List<T> with the exact same contents, and they will report themselves as unequal.
So far I have been able to use the "Banned API Analyzers" to prevent people from using List<T> in the shop, but I do not know how to specify a rule for <>z__ReadOnlyArray<T>, and even if I could, collection expressions hide the type that is being instantiated, so the analyzers will not flag those instantiations. (They do not flag IList<int> c5 = [5, 6]; which instantiates a List<int>.)
How can I control which types are instantiated by collection expressions when these expressions are used for the creation of collection interfaces?
The documentation explains how to create a new collection type which abides by the necessary contract, but it does not show any means of telling the compiler to use that type when creating instances to back the collection interfaces.
