Reputation: 1591
I initially had some code, which when simplified, looks like this:
var planets = new List<Planet>
{
new Planet {Id = 1, Name = "Mercury"},
new Planet {Id = 2, Name = "Venus"},
};
I got into a scenario where the list was being populated all at once, but the reads weren't fast enough. And so, I changed this to use a SortedList instead.
I later realized that I could rewrite it like this
var planets = new SortedList<int, Planet>
{
{1, new Planet {Id = 1, Name = "Mercury"}},
{2, new Planet {Id = 2, Name = "Venus"}},
//in my actual code, i am reading the ids from a db
};
But before I got to this approach, I had the code written like this
var planets = new SortedList<int, Planet>
{
Keys = {1, 2},
Values =
{
new Planet {Id = 1, Name = "Mercury"},
new Planet {Id = 2, Name = "Venus"},
}
};
which gives me this exception
System.NotSupportedException: This operation is not supported on SortedList
nested types because they require modifying the original SortedList.
at System.ThrowHelper.ThrowNotSupportedException(ExceptionResource resource)
at System.Collections.Generic.SortedList`2.KeyList.Add(TKey key)
which I found to be very strange, coz IMHO, I wasn't really modifying the "original SortedList" as it claims, and what "nested types" is it talking about? Is it the list of keys internal to the SortedList
?
I see then that the Keys
and Values
properties in SortedList
don't actually have setters. They are read-only properties, and yet, I don't get a compile-time error. I am allowed to make a set call, as I can see in the stack trace with KeyList.Add
. I feel the only reason why this fails is because of an explicit check within SortedList
, which seems bizarre to me!
For instance
var str = new String {Length = 0};
gives me a compile-time error as expected, since Length is a read-only property, as does planets.Keys = null;
Someone please tell me - what simple fact am I overlooking here?
Upvotes: 2
Views: 1063
Reputation: 1591
My initial query about the SortedList
has now minimized to this concern about array, collection & object initializers, and the way the compiler interprets them differently. Thanks to @Haney again for the first answer to guide me towards this point of view, and to ILSpy for these insights.
Here are some array and collection initializers:
int[] a = { 1, 2, 3 };
int[] b = new int[] { 1, 2, 3 };
IList<int> c = { 1, 2, 3 };
IList<int> d = new int[] { 1, 2, 3 };
They all look kind of similar. Here, the compiler produces the exact same output for a & b. For c, we will get this compile-time error:
Can only use array initializer expressions to assign to array types. Try using a new expression instead.
which makes sense since we shouldn't use array initializers for collections. But then, d produces the exact same result as a & b. And I thought that was an array initializer as well. Apparently not.
Now consider this class
class MyCollectionContainer
{
public int[] MyIntArray { get; set; }
public IList<int> MyList { get; set; }
}
and this code that operates on it
var containerA = new MyCollectionContainer { MyIntArray = { 1, 2, 3 } };
var containerB = new MyCollectionContainer { MyIntArray = new int[]{ 1, 2, 3 } };
var containerC = new MyCollectionContainer { MyList = { 1, 2, 3 } };
var containerD = new MyCollectionContainer { MyList = new int[]{ 1, 2, 3 } };
containerA gives this compile-time error:
Cannot initialize object of type 'int[]' with a collection initializer
For containerB, the compiler effectively converts it into this code:
MyCollectionContainer myCollectionContainer = new MyCollectionContainer();
myCollectionContainer.MyIntArray = new int[] {1, 2, 3};
For containerD, its pretty much the same, barring the fact that its another property that gets initialized:
MyCollectionContainer myCollectionContainer = new MyCollectionContainer();
myCollectionContainer.MyList = new int[] {1, 2, 3};
For containerC, the compiler morphs it into:
MyCollectionContainer myCollectionContainer = new MyCollectionContainer();
myCollectionContainer.MyList.Add(1);
myCollectionContainer.MyList.Add(2);
myCollectionContainer.MyList.Add(3);
This results in a run-time NullReferenceException
since MyList
is not initialized.
This means the only valid ways to initialize the collection container object here is containerB and containerD. To me, this clearly shows that object initializers are different when compared to array & collection initializers, in the way the compiler interprets them.
Upvotes: 0
Reputation: 34802
The code that you've written is comparable to this:
var planets = new SortedList<int, Planet>();
planets.Keys.Add(1);
planets.Keys.Add(2);
planets.Values.Add(new Planet { Id = 1, Name = "Mercury" });
planets.Values.Add(new Planet { Id = 2, Name = "Venus" });
SortedList
requires that you add the value and key at the same time via SortedList<TKey, TValue>.Add(TKey key, TValue value)
method, so that it can sort the value by the key. The implementation of the IList<T>
which is used for Keys
and Values
internally does not support adding a respective key or value independently via the IList<T>.Add(T value)
method.
You should be able to reproduce this error by calling Keys.Add(...)
or Values.Add(...)
Upvotes: 3