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?
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(...)
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.
Related
Instead of doing my old non OOP way I am trying to do what people claim is best. I need to store about 9 different Int arrays of differing lengths. I also need to associate them with a String Name "this is called etc.." I was thinking it would make sense to store that all into a class object so I can cleanly iterate through them later on without looking to two different places using the same for loop iterator.
Example:
public class Thing
{
public List<int> SDNA {get; set;}
public string Name {get; set;}
}
List<Thing> things = new List<Thing>
{
new Thing { SDNA = {2,4,5,7,9,11},Name = "First Thing"}
}
I get a null ref exception (I am assuming its cause of the list within a class somehow) I tried creating a list this way to clear the null ref but it had some other errors.
List<Thing> things = new List<Thing>();
things.Add(new Thing() {SDNA = {2,4,5,7,9,11},Name = "The first things name"});
Errors of invalid token etc. Should I just do it with two different stored arrays, one for names and a jagged array for the Ints and then reference them each? That feels ugly to me. Why can't I store them all into one thing?
Thanks!
In the simplest case if you want just to have name to value (array) association, you can try using a simple Dictionary, e.g.
private Dictionary<string, List<int>> things = new Dictionary<string, List<int>>() {
{"First thing", new List<int>() {2, 4, 5, 7, 9, 11}},
};
then you can use it
// Add new thing
things.Add("Some other thing", new List<int>() {1, 2, 3, 4, 5});
// Try get thing
if (things.TryGetValue("First thing", out var list)) {
// "First thing" exists, list is corresponding value
}
else {
// "First thing" doesn't found
}
// Remove "Some other thing"
things.Remove("Some other thing");
// Iterate over all {Key, Value} pairs (let's print them):
foreach (var pair in things)
Console.WriteLine($"{pair.Key} :: [{string.Join(", ", pair.Value)}]");
However, if Thing is not just SDNA + Name combination (more properties, methods are expected) I suggest
private Dictionary<string, Thing> things
declaration
Is is possible to combine a List initializer and object initializer at the same time?
Given the following class definition:
class MyList : List<int>
{
public string Text { get; set; }
}
// we can do this
var obj1 = new MyList() { Text="Hello" };
// we can also do that
var obj2 = new MyList() { 1, 2, 3 };
// but this one doesn't compile
//var obj3 = new MyList() { Text="Hello", 1, 2, 3 };
Is this by design or is it just a bug or missing feature of the c# compiler?
No, looking at the definitions from section 7.6.10 of the C# spec, an object-or-collection-initializer expression is either an object-initializer or a collection-initializer.
An object-initializer is composed of multiple member-initializers, each of which is of the form initializer = initializer-value whereas a collection-initializer is composed of multiple element-initializers, each of which is a non-assigment-expression.
So it looks like it's by design - possibly for the sake of simplicity. I can't say I've ever wanted to do this, to be honest. (I usually wouldn't derive from List<int> to start with - I'd compose it instead.) I would really hate to see:
var obj3 = new MyList { 1, 2, Text = "Hello", 3, 4 };
EDIT: If you really, really want to enable this, you could put this in the class:
class MyList : List<int>
{
public string Text { get; set; }
public MyList Values { get { return this; } }
}
at which point you could write:
var obj3 = new MyList { Text = "Hello", Values = { 1, 2, 3, 4 } };
No, it's a not a bug. It is by design of the language.
When you write
var obj1 = new MyList() { Text="Hello" };
this is effectively translated by the compiler to
MyList temp = new MyList();
temp.Text = "Hello";
MyList obj = temp;
When you write
var obj2 = new MyList() { 1, 2, 3 };
this is effectively translated by the compiler to
MyList temp = new MyList();
temp.Add(1);
temp.Add(2);
temp.Add(3);
MyList obj2 = temp;
Note that in the first case you are using an object initializer, but in the second case you are using a collection initializer. There is no such thing as an object-and-collection intializer. You are either initializing the properties of your object, or you are initializing the collection. You can not do both, this is by design.
Also, you shouldn't derive from List<T>. See: Inheriting List<T> to implement collections a bad idea?
If you want to get something like this functionality, consider making a constructor argument:
var x = new CustomList("Hello World") { 1, 2, 3 }
If I want to instantiate a array the syntax is
int[] items = new int[] { 1, 2, 3 };
and the shortcut is
int[] items = {1,2,3};
Now I want to do the same to a List.
Question:
why does this work:
List<int> items = new int[] { 1, 2, 3 }.ToList();
but not this:
List<int> items = { 1, 2, 3 }; //invalid
or
List<int> items = { 1, 2, 3 }.ToList(); //invalid
The syntax
int[] array = {1,2,3};
is special syntactic sugar for array initialization. {1,2,3} is not itself an array yet.
This line
List<int> list = new[] {1,2,3}.ToList();
works because the expression new[] {1,2,3} returns an int[], which implements IEnumerable<int> and so you can call ToList() on it.
In the specs that's the difference between 12.6 Array initializers and 7.5.10.2 Array creation expressions.
List<T> has a constructor that takes an IEnumerable<T> as argument to initialize the list's content with. So you can either call
List<int> list = new List<int>(new[]{1,2,3});
or use the collection initializer syntax sugar:
List<int> list = new List<int> {1,2,3};
which is converted by the compiler to something like this:
var tmp = new List<int>();
tmp.Add(1);
tmp.Add(2);
tmp.Add(3);
List<int> list = tmp;
Does anyone know if it's possible to create a new IEnumerable by using an array parameter to exclude values.
For instance, below is an example of how I imagine it would look.
class Item
{
public int id { get; set; }
public string name { get; set; }
}
IEnumerable looks like this:
item1 {id = 1}
item2 {id = 2}
item3 {id = 3}
I want to create a new IEnumerable but exclude the id numbers in the array.
Made up code to suggest idea:
Int32[] arrayList = {1,2};
var newIEnumerable = _exisitingIEnumerable.Where(o => (o.id NOT IN arrayList));
Looking at your question again, when the element type of _exisitingIEnumerable is not the same as that of arrayList, you will need to use Where to filter out the elements of arrayList
_exisitingIEnumerable.Where(o => !arrayList.Contains(o.Id))
Original answer:
_exisitingIEnumerable.Except(arrayList)
will return the distinct elements from _exisitingIEnumerable that are not in arrayList
If you need duplicates, you can use
_exisitingIEnumerable.Where(o => !arrayList.Contains(o))
What's wrong with the approach you suggested in the question? You can use Where and check if the array contains the value. Below the example using List as a target collection:
var myList = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
int[] myArray = { 1, 2, 3 };
var result = new List<int>(myList.Where(n => !myArray.Contains(n)));
I have two arrays, x and y, where y is the value of the tens of every element in x. Now, I want to sort y. But, the order of y will be different of x's. So, I can't tell after sorting which element in y was related to, for instance, x[0].
I want a "double sorting" maybe.
Array.Sort has an overload that accepts two arrays; one for the keys, and one for the items. The items of both are sorted according to the keys array:
int[] keys = { 1, 4, 3, 2, 5 };
string[] items = { "abc", "def", "ghi", "jkl", "mno" };
Array.Sort(keys, items);
foreach (int key in keys) {
Console.WriteLine(key); // 1, 2, 3, 4, 5
}
foreach (string item in items) {
Console.WriteLine(item); // abc, jkl, ghi, def, mno
}
So in your case, it sounds like you want:
Array.Sort(y,x); // or Sort(x,y); - it isn't 100% clear
How about?
var selectedArr = new int[] { 1, 3, 5, 7, 9 };
var unorderArr = new int[] { 9, 7, 5, 3, 1 };
var orderedArr = unorderArr.OrderBy(o => selectedArr.IndexOf(o));
If we have two arrays of complex objects and want to sort them according to one of the two arrays then we can use the next approach:
// We want to sort "people" array by "Name" and
// accordingly to it reorder "countries" array.
Person[] people = new Person[]
{
new Person {Name = "Fill"},
new Person {Name = "Will"},
new Person {Name = "Bill"},
};
Country[] countries = new Country[]
{
new Country {Name = "Canada"},
new Country {Name = "UK"},
new Country {Name = "USA"}
};
// Here we sort "people" array, but together with each "Person"
// in sorted array we store its "index" in unsorted array. Then we
// will use this "index" to reorder items in "countries" array.
var sorted = people
.Select((person, index) => new {person, index})
.OrderBy(x => x.person.Name)
.ToArray();
// Here "people" array is sorted by "Name", and
// "contries" array is reordered accordingly to it.
people = sorted.Select(x => x.person).ToArray();
countries = sorted.Select(x => countries[x.index]).ToArray();
Another approach is to use overload of the method Array.Sort with IComparer. At first we should implement IComparer:
private class PeopleComparer : IComparer<Person>
{
public int Compare(Person x, Person y)
{
return x.Name.CompareTo(y.Name);
}
}
And then we can sort two our arrays:
Array.Sort(people, countries, new PeopleComparer());
Here is complete sample that demonstrates these two approaches.
If y is always the tens value of x, y probably shouldn't exist - you should probably just calculate it's value directly off of x when needed.
In general, sorting parallel arrays is only possible (without hand rolling a sort algorithm) when the sort algorithm takes a custom "swap" function, which you can implement in terms of swapping elements in both arrays simultaneously. std::sort in C++ and qsort in C don't allow this.
Also in the general case, consider a single array where the element is a pair of items, rather than a parallel array for each item. This makes using "standard" algorithms easier.