Getting a ref/address from a list in C# - c#

I am aware that C# does not deal with pointers but I wonder if I can get a ref to a list when I know its first element?
For example:
Let's say I have a list defined as
List<T> abc
If I have abc[0], can I get a reference of abc?
I am new to C#, I apologize if my question seems weird. In C/C++, I can get the address of an array abc by using &abc[0]. Does C# provide us with similar tool that help us refer back to the collection itself when we know one item in the collection?
Thanks,

This is now possible, starting with .NET 5.0, by using the System.Runtime.InteropServices.CollectionsMarshal.AsSpan method.
As indicated by the documentation, items should not be added to or removed from the list while using the span or item references taken from the span. Technically this should also be extended to say that the Capacity should not be changed nor should TrimExcess() be called. If these operations are used while actively using the span or its references, then the internal memory of the list may no longer be the same as the memory in the span.
// Create a simple list with three items.
var list = new List<int>();
list.Add(123);
list.Add(456);
list.Add(789);
// Print list to console.
Console.WriteLine("List items:");
foreach (var item in list)
Console.WriteLine(item);
Console.WriteLine();
// Get a reference to the second item in the list.
// WARNING: DO NOT ADD/REMOVE ITEMS FROM THE LIST WHILE USING THIS SPAN
// OR ANY REFERENCES DERIVED FROM THIS SPAN!
var listSpan = CollectionsMarshal.AsSpan(list);
ref var secondItem = ref listSpan[1];
Console.WriteLine($"Referenced value (original): {secondItem}");
// Change the referenced list item.
secondItem = 0;
Console.WriteLine($"Referenced value (modified): {secondItem}");
Console.WriteLine();
// Print the list to console.
Console.WriteLine("List items:");
foreach (var item in list)
Console.WriteLine(item);
Console.WriteLine();
You should get output like this:
List items:
123
456
789
Referenced value (original): 456
Referenced value (modified): 0
List items:
123
0
789

Collections don't work the same way in C# as they do in C++, for example you can add the same object to multiple different collections and so it doesn't really make sense to ask to get a reference to the list that an object is contained in, as it could be in many lists (or none, or even in the same list multiple times)
object myObject = new object();
List<object> list = new List<object>();
list.Add(myObject);
object[] someArray = new object[] { myObject };
Assert.AreEqual(list[0], someArray[0]);
If it helps you can think of lists in C# as being lists of pointers references to the objects being stored where the pointer itself is hidden from you, although understand that in reality the implementation may be more complicated (and is also irrelevant).
If there is a relationship between the objects in a list and the list contents of that list then its up to you to explicitly declare and keep track of what that realtionsip is, for example through a Parent property on the object in the list
List<T> myList = new List<T>();
// Whenever an item is added to myList set the Parent property
myList.Add(item);
item.Parent = myList;
This is what Windows Forms does in order to maintain the relationship between the controls in a container, and the container in which those controls are contained. Obviously you should decide what to do if someone tries to add the same object to multiple lists.

Not unless the type of abc[0] explicitly holds a reference to the list. In fact you cannot do that in C++ either without an explicit reference.
Think about it, in C++ if you can expect firstElemPtr == arrayPtr it is just because arrays store the elements that way and it only works for arrays; everything else is just by accident.
Now consider any list structure that allocates something else (maybe element count) before the pointer to the first element. Your assumption will not work anymore.

If you are designing the type of the items in the collection, then you can add a property to the item's type that "points" to the containing list; when you construct each item, pass in the containing list and save this in the property.
Something like this:
class ListItem
{
public List<ListItem> Parent { get; set; }
public ListItem(List<ListItem> parent)
{
Parent = parent;
}
}
ListItem listItem = new ListItem(abc);
abc.Add(listItem);
// Get collection from item.
List<T> def = listItem.Parent;

Consider:
unsafe static void Main() {
int[] arr = new int[100];
fixed(int* ptr = arr) {
// ptr is a pointer to the zeroth item in the array
}
}
However, unsafe code is not all that common in c# and should be limited to performance-critical regions (and even then used sparingly). In particular, note that we have "pinned" the array by doing this - and note that ptr is only reliable while it is pinned. Outside of the fixed block, GC is free to relocate the array, making ptr invalid.
Clarification: I don't suggest you should do this, but: such things are entirely possible.

List is defined somewhat like this:
public class List<T> : IList<T> blabla
{
private T [] data;
public T this[int index] {
get { return data[index]; }
set { data[index]=value; }
}
... blabla
}
YES, it is NOT linked list of any kind. So you must use it accordingly. Though since .NET value types are small and class types are all references, there usually no heavy array-copy operations as it is possible with C++ for example so such implementation of general-puprose collection is good enough (unless abused). Vector could be a better name from academic standpoint, but it's not.
You would not be able to get a reference to data array, as it's private variable. Nor you would have any need for this. If you need enumerator, use GetEnumerator explicitely or foreach for implicit use.

Related

Public List error when setting up a table view in Visual Studio

I'm trying to work on a table view in Xcode and Visual studio, and am setting it up to import data, but I keep getting an error in the List.
using System.Collections.Generic;
using AppKit;
namespace HCATester
{
public class NormsLogDataSource : NSTableViewDataSource
{
public NormsLogDataSource(){}
public List Norms = new List();
public override nint GetRowCount(NSTableView tableView)
{
return Norms.Count;
}
}
}
Whenever I select it to see what's wrong, this is what I get:
Implements the System.Collections.Generic.IList interface. The size of a List is dynamically increased as required. A List is not guaranteed to be sorted. It is the programmer's responsibility to sort the List prior to performing operations (such as BinarySearch) that require a List to be sorted. Indexing operations are required to perform in constant access time; that is, O(1).
Your problem is with the declaration of your List. You are using the System.Collections.Generic namespace which (among other collections) contains the generic List<T>. But you aren't specifying the generic type parameter.
You see, before we had generics, we used ArrayList which just held a collection of objects, so we'd always need to cast it to what we actually wanted. For example, if we want a ArrayList of integers we could declare our list like this:
ArrayList list = new ArrayList();
list.Add(1);
list.Add(2);
list.Add(3);
But when using it we would need to cast our item from object to whatever type we wanted like so:
int item1 = (int) list[0];
int item2 = (int) list[1];
// ...
This gets confusing and error prone, for example if a Method takes an ArrayList as a parameter you'd always need to make sure every element of said ArrayList was of the right type.
In come Generics and the generic List<T>, with it we can define a strongly typed List. The same example as above but using List is much easier to read and understand:
List<int> list = new List<int>();
list.Add(1);
list.Add(1);
list.Add(1);
int item1 = list[0];
int item2 = list[1];
// ...
Now we don't need to cast our object, as we already know what datatype it is. This also adds a lot more security, as we can define what types are valid inside a List.
Now to answer your question, you need to specify the datatype you are using, you said string so I'll show you how. All you need to do is replace your line where you are declaring the List with this:
public List<string> Norms = new List<string>();
P.S the generic () part of a class can be read as "of" or "for", e.g List<T> would be read as "A List of T", List<int> would be "A List of Integers". And ViewModel<TView> would be read as "A ViewModel for TView"

list property containing two types (string/int)

I need to have a property that will be an array that can hold both ints and strings.
if i set the property to an array of ints it should be ints so when I am searching through this array the search will be fast, and at odd times this property will also contain strings which the search will be slow.
Is there any other way other than the following to have a list that contain native types
two properties one for ints and one for strings
use List< object >
UPDATE:
The use-case is as follow. I have a database field [ReferenceNumber] that holds the values (integers and strings) and another field [SourceID] (used for other things) which can be used to determine if record holds an int or string.
I will be fetching collections of these records based on the source id, of course depending on what the source is, the list either will be integers or strings. Then I will go through this collection looking for certain reference numbers, if they exist not add them or they dont then add them. I will be pre-fetching a lot of records instead of hitting the database over and over.
so for example if i get a list for sourceid =1 that means they are ints and if searching i want the underline list to be int so the search will be fast. and if sourceid say is 2 which means they are strings and very rare its okay if the search is slow because those number of records are not that many and a performance hit on searching through strings is okay.
I will go through this collection looking for certain reference numbers, if they exist not add them or they dont then add them.
It sounds to me like you don't need a List<>, but rather a HashSet<>. Simply use a HashSet<object>, and Add() all the items, and the collection will ignore duplicate items. It will be super-fast, regardless of whether you're dealing with ints or strings.
On my computer, the following code shows that it takes about 50 milliseconds to populate an initial 400,000 unique strings in the hashset, and about 2 milliseconds to add an additional 10,000 random strings:
var sw = new Stopwatch();
var initial= Enumerable.Range(1, 400000).Select(i => i.ToString()).ToList();
sw.Start();
var set = new HashSet<object>(initial);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
var random = new Random();
var additional = Enumerable.Range(1, 10000).Select(i => random.Next(1000000).ToString()).ToList();
sw.Restart();
foreach (var item in additional)
{
set.Add(item);
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Also, in case it's important, HashSet<>s do retain order of insertion.
The only other thing I would suggest is a custom object that implements IComparable
class Multitype: IComparable
{
public int? Number { get; set; }
public string Words {get; set; }
public int CompareTo(object obj)
{
Multitype other = obj as Multitype;
if (Number != null && other != null && other.Number != null)
{
//...
}
else
{
//...
}
}
}
There will be some extra comparison steps between numbers, but not as much as string parsing.
Are you storing a ton of data, is that performance difference really going to matter?
It's possible to use generics if you implement them on the class. Not sure if this solves your problem. Would be interested to hear the real-world example of a property that can have different types.
class Foo<T>
{
public List<T> GenericList { get; set; }
public Foo()
{
this.GenericList = new List<T>();
}
}
If by "use List" you mean the object primitive or provided System.Object, that is an option, but I think it would behoove you to make your own wrapper object -- IntString or similar -- that would handle everything for you. It should implement IComparable, as the other gal mentioned.
You can increase the efficiency of sorting your object in collections by writing a CompareTo method that does exactly what you need it to. Writing a good CompareTo method is a whole can of worms in itself, so you should probably start a new question for that, if that's what you want.
If you're looking for a property that is strongly typed as a List<Int> or List<String> at instantiation, but can change afterwards, then you want an interface. IList exists, but won't help you, since that must also be strongly typed upon declaration. You should probably make something like IIntStringList that can only be one of List<Int> or List<String>.
Sorry this answer doesn't have that many details (I need to leave the office now), but I hope I've set you on the right track.

ToList()-- does it create a new list?

Let's say I have a class
public class MyObject
{
public int SimpleInt{get;set;}
}
And I have a List<MyObject>, and I ToList() it and then change one of the SimpleInt, will my change be propagated back to the original list. In other words, what would be the output of the following method?
public void RunChangeList()
{
var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}};
var whatInt = ChangeToList(objs );
}
public int ChangeToList(List<MyObject> objects)
{
var objectList = objects.ToList();
objectList[0].SimpleInt=5;
return objects[0].SimpleInt;
}
Why?
P/S: I'm sorry if it seems obvious to find out. But I don't have compiler with me now...
Yes, ToList will create a new list, but because in this case MyObject is a reference type then the new list will contain references to the same objects as the original list.
Updating the SimpleInt property of an object referenced in the new list will also affect the equivalent object in the original list.
(If MyObject was declared as a struct rather than a class then the new list would contain copies of the elements in the original list, and updating a property of an element in the new list would not affect the equivalent element in the original list.)
From the Reflector'd source:
public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
return new List<TSource>(source);
}
So yes, your original list won't be updated (i.e. additions or removals) however the referenced objects will.
ToList will always create a new list, which will not reflect any subsequent changes to the collection.
However, it will reflect changes to the objects themselves (Unless they're mutable structs).
In other words, if you replace an object in the original list with a different object, the ToList will still contain the first object.
However, if you modify one of the objects in the original list, the ToList will still contain the same (modified) object.
Yes, it creates a new list. This is by design.
The list will contain the same results as the original enumerable sequence, but materialized into a persistent (in-memory) collection. This allows you to consume the results multiple times without incurring the cost of recomputing the sequence.
The beauty of LINQ sequences is that they are composable. Often, the IEnumerable<T> you get is the result of combining multiple filtering, ordering, and/or projection operations. Extension methods like ToList() and ToArray() allow you to convert the computed sequence into a standard collection.
The accepted answer correctly addresses the OP's question based on his example. However, it only applies when ToList is applied to a concrete collection; it does not hold when the elements of the source sequence have yet to be instantiated (due to deferred execution). In case of the latter, you might get a new set of items each time you call ToList (or enumerate the sequence).
Here is an adaptation of the OP's code to demonstrate this behaviour:
public static void RunChangeList()
{
var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 });
var whatInt = ChangeToList(objs); // whatInt gets 0
}
public static int ChangeToList(IEnumerable<MyObject> objects)
{
var objectList = objects.ToList();
objectList.First().SimpleInt = 5;
return objects.First().SimpleInt;
}
Whilst the above code may appear contrived, this behaviour can appear as a subtle bug in other scenarios. See my other example for a situation where it causes tasks to get spawned repeatedly.
A new list is created but the items in it are references to the orginal items (just like in the original list). Changes to the list itself are independent, but to the items will find the change in both lists.
Just stumble upon this old post and thought of adding my two cents. Generally, if I am in doubt, I quickly use the GetHashCode() method on any object to check the identities. So for above -
public class MyObject
{
public int SimpleInt { get; set; }
}
class Program
{
public static void RunChangeList()
{
var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } };
Console.WriteLine("objs: {0}", objs.GetHashCode());
Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode());
var whatInt = ChangeToList(objs);
Console.WriteLine("whatInt: {0}", whatInt.GetHashCode());
}
public static int ChangeToList(List<MyObject> objects)
{
Console.WriteLine("objects: {0}", objects.GetHashCode());
Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode());
var objectList = objects.ToList();
Console.WriteLine("objectList: {0}", objectList.GetHashCode());
Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode());
objectList[0].SimpleInt = 5;
return objects[0].SimpleInt;
}
private static void Main(string[] args)
{
RunChangeList();
Console.ReadLine();
}
And answer on my machine -
objs: 45653674
objs[0]: 41149443
objects: 45653674
objects[0]: 41149443
objectList: 39785641
objectList[0]: 41149443
whatInt: 5
So essentially the object that list carries remain the same in above code. Hope the approach helps.
I think that this is equivalent to asking if ToList does a deep or shallow copy. As ToList has no way to clone MyObject, it must do a shallow copy, so the created list contains the same references as the original one, so the code returns 5.
ToList will create a brand new list.
If the items in the list are value types, they will be directly updated, if they are reference types, any changes will be reflected back in the referenced objects.
In the case where the source object is a true IEnumerable (i.e. not just a collection packaged an as enumerable), ToList() may NOT return the same object references as in the original IEnumerable. It will return a new List of objects, but those objects may not be the same or even Equal to the objects yielded by the IEnumerable when it is enumerated again
var objectList = objects.ToList();
objectList[0].SimpleInt=5;
This will update the original object as well. The new list will contain references to the objects contained within it, just like the original list. You can change the elements either and the update will be reflected in the other.
Now if you update a list (adding or deleting an item) that will not be reflected in the other list.
I don't see anywhere in the documentation that ToList() is always guaranteed to return a new list. If an IEnumerable is a List, it may be more efficient to check for this and simply return the same List.
The worry is that sometimes you may want to be absolutely sure that the returned List is != to the original List. Because Microsoft doesn't document that ToList will return a new List, we can't be sure (unless someone found that documentation). It could also change in the future, even if it works now.
new List(IEnumerable enumerablestuff) is guaranteed to return a new List. I would use this instead.

Can't add/remove items from a collection while foreach is iterating over it

If I make my own implementation of IEnumerator interface, then I am able ( inside foreach statement )to add or remove items from a albumsList without generating an exception.But if foreach statement uses IEnumerator supplied by albumsList, then trying to add/delete ( inside the foreach )items from albumsList will result in exception:
class Program
{
static void Main(string[] args)
{
string[] rockAlbums = { "rock", "roll", "rain dogs" };
ArrayList albumsList = new ArrayList(rockAlbums);
AlbumsCollection ac = new AlbumsCollection(albumsList);
foreach (string item in ac)
{
Console.WriteLine(item);
albumsList.Remove(item); //works
}
foreach (string item in albumsList)
{
albumsList.Remove(item); //exception
}
}
class MyEnumerator : IEnumerator
{
ArrayList table;
int _current = -1;
public Object Current
{
get
{
return table[_current];
}
}
public bool MoveNext()
{
if (_current + 1 < table.Count)
{
_current++;
return true;
}
else
return false;
}
public void Reset()
{
_current = -1;
}
public MyEnumerator(ArrayList albums)
{
this.table = albums;
}
}
class AlbumsCollection : IEnumerable
{
public ArrayList albums;
public IEnumerator GetEnumerator()
{
return new MyEnumerator(this.albums);
}
public AlbumsCollection(ArrayList albums)
{
this.albums = albums;
}
}
}
a) I assume code that throws exception ( when using IEnumerator implementation A supplied by albumsList ) is located inside A?
b) If I want to be able to add/remove items from a collection ( while foreach is iterating over it), will I always need to provide my own implementation of IEnumerator interface, or can albumsList be set to allow adding/removing items?
thank you
Easiest way is to either reverse through the items like for(int i = items.Count-1; i >=0; i--), or loop once, gather all the items to remove in a list, then loop through the items to remove, removing them from the original list.
Generally it's discouraged to design collection classes that allow you to modify the collection while enumerating, unless your intention is to design something thread-safe specifically so that this is possible (e.g., adding from one thread while enumerating from another).
The reasons are myriad. Here's one.
Your MyEnumerator class works by incrementing an internal counter. Its Current property exposes the value at the given index in an ArrayList. What this means is that enumerating over the collection and removing "each" item will actually not work as expected (i.e., it won't remove every item in the list).
Consider this possibility:
The code you posted will actually do this:
You start by incrementing your index to 0, which gives you a Current of "rock." You remove "rock."
Now the collection has ["roll", "rain dogs"] and you increment your index to 1, making Current equal to "rain dogs" (NOT "roll"). Next, you remove "rain dogs."
Now the collection has ["roll"], and you increment your index to 2 (which is > Count); so your enumerator thinks it's finished.
There are other reasons this is a problematic implementation, though. For instance someone using your code might not understand how your enumerator works (nor should they -- the implementation should really not matter), and therefore not realize that the cost of calling Remove within a foreach block incurs the penalty of IndexOf -- i.e., a linear search -- on every iteration (see the MSDN documentation on ArrayList.Remove to verify this).
Basically, what I'm getting at is: you don't want to be able to remove items from within a foreach loop (again, unless you're designing something thread-safe... maybe).
OK, so what is the alternative? Here are a few points to get you started:
Don't design your collection to allow -- let alone expect -- modification within an enumeration. It leads to curious behavior such as the example I provided above.
Instead, if you want to provide bulk removal capabilities, consider methods such as Clear (to remove all items) or RemoveAll (to remove items matching a specified filter).
These bulk-removal methods can be implemented fairly easily. ArrayList already has a Clear method, as do most of the collection classes you might use in .NET. Otherwise, if your internal collection is indexed, a common method to remove multiple items is by enumerating from the top index using a for loop and calling RemoveAt on indices where removal is desired (notice this fixes two problems at once: by going backwards from the top, you ensure accessing each item in the collection; moreover, by using RemoveAt instead of Remove, you avoid the penalty of repeated linear searches).
As an added note, I would strongly encourage steering clear of non-generic collections such as ArrayList to begin with. Go with strongly typed, generic counterparts such as List(Of Album) instead (assuming you had an Album class -- otherwise, List(Of String) is still more typesafe than ArrayList).
Suppose I have a collection, an array for that matter
int[] a = { 1, 2, 3, 4, 5 };
I have a function
public IList<int> myiterator()
{
List<int> lst = new List<int>();
for (int i = 0; i <= 4; i++)
{
lst.Add(a[i]);
}
return lst;
}
Now i call this function and iterate over and try to add
var a = myiterator1();
foreach (var a1 in a)
{
a.Add(29);
}
Will cause a runtime exception
Here thing to notice is that if we are allowed to add for each element
in list
list will become something like {1,2,3,4,5,6}
then for every element and every newly added we keep on adding coz of that
we will be stuck in a infinite operation as it will again be repeated for every element
From the MSDN documentation for INotifyCollectionChanged:
You can enumerate over any collection
that implements the IEnumerable
interface. However, to set up dynamic
bindings so that insertions or
deletions in the collection update the
UI automatically, the collection must
implement the INotifyCollectionChanged
interface. This interface exposes the
CollectionChanged event that must be
raised whenever the underlying
collection changes.
WPF provides the
ObservableCollection<(Of <(T>)>)
class, which is a built-in
implementation of a data collection
that exposes the
INotifyCollectionChanged interface.
For an example, see How to: Create and
Bind to an ObservableCollection.
The individual data objects within the
collection must satisfy the
requirements described in the Binding
Sources Overview.
Before implementing your own
collection, consider using
ObservableCollection<(Of <(T>)>) or
one of the existing collection
classes, such as List<(Of <(T>)>),
Collection<(Of <(T>)>), and
BindingList<(Of <(T>)>), among many
others.
If you have an advanced scenario and
want to implement your own collection,
consider using IList, which provides a
non-generic collection of objects that
can be individually accessed by index
and provides the best performance.
Sounds to me that the problem is in the Collection itself, and not its Enumerator.

c# modifying structs in a List<T>

Short question: How can I modify individual items in a List? (or more precisely, members of a struct stored in a List?)
Full explanation:
First, the struct definitions used below:
public struct itemInfo
{
...(Strings, Chars, boring)...
public String nameStr;
...(you get the idea, nothing fancy)...
public String subNum; //BTW this is the element I'm trying to sort on
}
public struct slotInfo
{
public Char catID;
public String sortName;
public Bitmap mainIcon;
public IList<itemInfo> subItems;
}
public struct catInfo
{
public Char catID;
public String catDesc;
public IList<slotInfo> items;
public int numItems;
}
catInfo[] gAllCats = new catInfo[31];
gAllCats is populated on load, and so on down the line as the program runs.
The issue arises when I want to sort the itemInfo objects in the subItems array.
I'm using LINQ to do this (because there doesn't seem to be any other reasonable way to sort lists of a non-builtin type).
So here's what I have:
foreach (slotInfo sInf in gAllCats[c].items)
{
var sortedSubItems =
from itemInfo iInf in sInf.subItems
orderby iInf.subNum ascending
select iInf;
IList<itemInfo> sortedSubTemp = new List<itemInfo();
foreach (itemInfo iInf in sortedSubItems)
{
sortedSubTemp.Add(iInf);
}
sInf.subItems.Clear();
sInf.subItems = sortedSubTemp; // ERROR: see below
}
The error is, "Cannot modify members of 'sInf' because it is a 'foreach iteration variable'".
a, this restriction makes no sense; isn't that a primary use of the foreach construct?
b, (also out of spite) what does Clear() do if not modify the list? (BTW, the List does get cleared, according to the debugger, if I remove the last line and run it.)
So I tried to take a different approach, and see if it worked using a regular for loop. (Apparently, this is only allowable because gAllCats[c].items is actually an IList; I don't think it will allow you to index a regular List this way.)
for (int s = 0; s < gAllCats[c].items.Count; s++)
{
var sortedSubItems =
from itemInfo iInf in gAllCats[c].items[s].subItems
orderby iInf.subNum ascending
select iInf;
IList<itemInfo> sortedSubTemp = new List<itemInfo>();
foreach (itemInfo iInf in sortedSubItems)
{
sortedSubTemp.Add(iInf);
}
//NOTE: the following two lines were incorrect in the original post
gAllCats[c].items[s].subItems.Clear();
gAllCats[c].items[s].subItems = sortedSubTemp; // ERROR: see below
}
This time, the error is, "Cannot modify the return value of 'System.Collections.Generic.IList.this[int]' because it is not a variable." Ugh! What is it, if not a variable? and when did it become a 'return value'?
I know there has to be a 'correct' way to do this; I'm coming to this from a C background and I know I could do it in C (albeit with a good bit of manual memory management.)
I searched around, and it seems that ArrayList has gone out of fashion in favor of generic types (I'm using 3.0) and I can't use an array since the size needs to be dynamic.
Looking at the for-loop approach, the reason (and solution) for this is given in the documentation for the compilation error:
An attempt was made to modify a value
type that is produced as the result of
an intermediate expression but is not
stored in a variable. This error can
occur when you attempt to directly
modify a struct in a generic
collection.
To modify the struct, first assign it
to a local variable, modify the
variable, then assign the variable
back to the item in the collection.
So, in your for-loop, change the following lines:
catSlots[s].subItems.Clear();
catSlots[s].subItems = sortedSubTemp; // ERROR: see below
...into:
slotInfo tempSlot = gAllCats[0].items[s];
tempSlot.subItems = sortedSubTemp;
gAllCats[0].items[s] = tempSlot;
I removed the call to the Clear method, since I don't think it adds anything.
The problem you are having in your foreach is that structs are value types, and as a result, the loop iteration variable isn't actually a reference to the struct in the list, but rather a copy of the struct.
My guess would be the compiler is forbidding you change it because it most likely would not do what you expect it to anyway.
subItems.Clear() is less of a problem, because altho the field may be a copy of the element in the list, it is also a reference to the list (shallow copy).
The simplest solution would probably be to change from a struct to a class for this. Or use a completely different approach with a for (int ix = 0; ix < ...; ix++), etc.
The foreach loop doesn't work because sInf is a copy of the struct inside items. Changing sInf will not change the "actual" struct in the list.
Clear works because you aren't changing sInf, you are changing the list inside sInf, and Ilist<T> will always be a reference type.
The same thing happens when you use the indexing operator on IList<T> - it returns a copy instead of the actual struct. If the compiler did allow catSlots[s].subItems = sortedSubTemp;, you'll be modifying the subItems of the copy, not the actual struct. Now you see why the compiler says the return value is not a variable - the copy cannot be referenced again.
There is a rather simple fix - operate on the copy, and then overwrite the original struct with your copy.
for (int s = 0; s < gAllCats[c].items.Count; s++)
{
var sortedSubItems =
from itemInfo iInf in gAllCats[c].items[s].subItems
orderby iInf.subNum ascending
select iInf;
IList<itemInfo> sortedSubTemp = new List<itemInfo>();
foreach (itemInfo iInf in sortedSubItems)
{
sortedSubTemp.Add(iInf);
}
var temp = catSlots[s];
temp.subItems = sortedSubTemp;
catSlots[s] = temp;
}
Yes, this results in two copy operations, but that's the price you pay for value semantics.
The two errors you specified have to do with the fact that you are using structs, which in C# are value types, not reference types.
You absolutely can use reference types in foreach loops. If you change your structs to classes, you can simply do this:
foreach(var item in gAllCats[c].items)
{
item.subItems = item.subItems.OrderBy(x => x.subNum).ToList();
}
With structs this would need to change to:
for(int i=0; i< gAllCats[c].items.Count; i++)
{
var newitem = gAllCats[c].items[i];
newitem.subItems = newitem.subItems.OrderBy(x => x.subNum).ToList();
gAllCats[c].items[i] = newitem;
}
The other answers have better information on why structs work different than classes, but I thought I could help with the sorting part.
If subItems was changed to a concrete List instead of the interface IList, then you'd be able to use the Sort method.
public List<itemInfo> subItems;
So your whole loop becomes:
foreach (slotInfo sInf in gAllCats[c].items)
sInf.subItems.Sort();
This won't require the contents of the struct to be modified at all (generally a good thing). The struct's members will still point to exactly the same objects.
Also, there are very few good reasons to use struct in C#. The GC is very, very good, and you'd be better off with class until you've demonstrated a memory allocation bottleneck in a profiler.
Even more succinctly, if items in gAllCats[c].items is also a List, you can write:
gAllCats[c].items.ForEach(i => i.subItems.Sort());
Edit: you give up too easily! :)
Sort is very easy to customise. For example:
var simpsons = new[]
{
new {Name = "Homer", Age = 37},
new {Name = "Bart", Age = 10},
new {Name = "Marge", Age = 36},
new {Name = "Grandpa", Age = int.MaxValue},
new {Name = "Lisa", Age = 8}
}
.ToList();
simpsons.Sort((a, b) => a.Age - b.Age);
That sorts from youngest to oldest. (Isn't the type inference good in C# 3?)

Categories