I'm using Unity version 2023.1.6. I have a custom collection that inherits from Collection<T>
. If I instantiate a collection and then try to use the .Add()
method I receive the following error :
System.NotSupportedException: Collection is read-only.
at System.Collections.ObjectModel.Collection`1[T].Add (T item) [0x0000d] in <46a5f68c38604d68a85231bbf55f6b8f>:0
I noticed that this error occurs only if I instantiate the collection using this constructor:
public Selection(params T[] selectionElements) : base(selectionElements) { }
I fixed the error by changing the constructor to this:
public Selection(params T[] selectionElements) : base(selectionElements.ToList()) { }
I don't know what LINQ does behind the scenes and even why this works (I just tried...), so I remain with a few more questions:
What's the problem with using the params
keyword?
Does params
create an array? If yes, why it's not ok to pass in an array as an IList<T>
? If it's not ok why there is no compile time error?
Why the LINQ method fixed the error?
There's no problem using the params
keyword, the problem is passing an array as an IList
and expecting to be able to add to it. This is an example of the C# base class library not doing the best job with the Liskov Substitution Principle (the 'L' in SOLID).
IList<T>
contains an 'IsReadOnly' property. An Array<T>
implements IList<T>
, but of course you can't add to an array - you would need to copy all the contents into a larger one first, which is exactly the job that List<T>
does. The IsReadOnly
property is true, and if you try to call Add()
it will throw an exception.
A separate IReadOnlyList<T>
was added later on to make it more obvious what's going on, but Array<T>
still implements the original IList<T>
interface for the sake of backwards compatibility.
Why does this work when you call selectionElements.ToList()
? ToList()
does the following:
List<T>
This means that you now have a List<T>
with all the same values in it, which implements IList<T>
and importantly is not read-only