Avoid object instantiation in loops c# - How this can be avoided? - c#

Here is the scenario
I know that class are reference types and Structures are value types
Below is Code1 which successfully outputs the Output1 which is expected functionality because as a new obj is created a new ref point is created and added to the persons list.
In code2. The same Object is getting assigned and as the code describes the foreach loop is actually updating the same reference that Obj is pointing all the time. At the end of for loop execution, final value is assigned to all the list items as in Output2
For case Code1 upon CAST tool review we are getting "Avoid object instantiation in loops".I know instantiation objects in for loop takes extra memory and performance too which is what I guess CAST tool is telling us. In such scenarios is there any solution that we can avoid new object instatiation inside the loop.
Using Structures is one solution based on the present scenario. But i would like to have any other ideas.
Code 1
public class person
{
public string name { get; set; }
public int age { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<person> personList = new List<person>();
for (int i = 0; i < 10; i++)
{
person Obj = Obj = new person();
Obj.name = "Robert" + i;
Obj.age = i * 10;
personList.Add(Obj);
}
foreach(person indv in personList)
{
Console.WriteLine(indv.name + indv.age);
}
}
}
Output
Robert00
Robert110
Robert220
Robert330
Robert440
Robert550
Robert660
Robert770
Robert880
Robert990
Code 2
List<person> personList = new List<person>();
person Obj = Obj = new person();
for (int i = 0; i < 10; i++)
{
Obj.name = "Robert" + i;
Obj.age = i * 10;
personList.Add(Obj);
}
foreach(person indv in personList)
{
Console.WriteLine(indv.name + indv.age);
}
Output 2
Robert990
Robert990
Robert990
Robert990
Robert990
Robert990
Robert990
Robert990
Robert990

I know instantiation objects in for loop takes extra memory and performance too which is what I guess CAST tool is telling us.
That's incorrect. An allocation will have the same "price" when used inside or outside that loop. I'm assuming your tool is warning you because allocating objects in a loop on each iteration may cause alot of objects to be instansiated, but that's exactly what's needed here. There is absolutely no need to avoid object allocation in this case.
I'd be more worried about that particular tool you're using and the advice it brings.

There is nothing wrong with instantiating those objects so I can't think why your tool is telling you that. At the end of the day the whole point of your code is to create a list of "person" objects. Whether you did it in a loop, or typed out all 10 instantiations in a row, it wouldn't make a difference. The loop is obviously better.
On another note though, you can really simplify this code by using linq, try writing it this way and see if your tool gives you the same warning:
List<person> personList = Enumerable.Range(1, 9).Select(x =>
new person { name = "Robert" + x, age = x * 10 }).ToList();

I mainly avoid instantiation in loops in cases where I want to use the object outside of the loop and it isn't being added to a collection. Additionally it wouldn't be necessary if I were instantiating in the loop and passing the object to another method within the loop. In that case you can instantiate outside of the loop and pass the values to the method within the loop. If this is all of the code that you're going to use, move the Console.WriteLine inside of the loop and don't bother instantiating inside of the loop.
But I get the impression that you're trying to create a collection of objects inside a loop to be used outside of that loop. In that case, your collection of objects isn't going to have any bigger memory footprint simply because you instantiated the objects inside the loop. As you can see, if you instantiate the object outside of the loop, you're simply assigning a new value to the same object and then adding a reference to the same object multiple times to the array. You'll need to instantiate each object in the array no matter what you do.

Related

Release memory between foreach iterations in release builds?

UPDATE
Obviously, the original question was confusing so I'll try to simplify it.
I'm working with a complex algorithme that is providing a list of object (let's say a company).
For each of those company I will have to load a large amount of data (let's say a list of employee).
public class Company
{
public string Name { get; set; } = "";
public List<Employee> EmployeeList { get; set; } = new List<Employee>();
}
public class Employee
{
public string FirstName { get; set; } = "Random first name";
public string LastName { get; set; } = "Random last name";
}
public MemoryTest()
{
//Simulate the complex algorithme...
//I can't change how I get that list and my question ain't about this part.
List<Company> companyList = new List<Company>();
for (int i = 0; i < 50000; i++)
{
companyList.Add(new Company() { Name = "Random company name " + i });
}
//Simulate the details loading. This is where the memory gets filled
foreach (Company company in companyList)
{
company.EmployeeList.AddRange(new Employee[25000]);
//Do some calculation and save to DB...
}
}
The problem with this code is that the memory allocated during each iteration won't be released until the end of the loop.
After reading this article I had hopes that the JIT would be able to determine a company reference would not be used after an iteration since the companyList isn't use beyond the foreach:
In release builds, the JIT is able to look at the program structure to work out the last point
within the execution that a variable can be used by the method and will discard it when it is
no longer required.
... but sadly, the JIT doesn't extrapolate that far.
In order to use a few memory as possible, my question is the following: Is there a way the loop thru a collection AND to remove reference to the element between each iteration?
Here's a more generic example if you don't want to work with Company / Employee
Dictionary<int, List<string>> dict = new Dictionary<int, List<string>>();
for (int i = 0; i < 100000; i++)
{
dict.Add(i, new List<string>());
}
foreach (var item in dict)
{
item.Value.AddRange(new string[25000]);
}
I'm going to revise me original answer here. Not to beat the proverbial dead horse but I just want to emphasize that if you're having to pop items out of a collection to keep the collection from rooting the objects in memory then it's almost certain you've got some issues with the design of the your application. There might be some scenarios where that's a great design but I say most often it's not.
Lets take Smurf's company scenario and change it slightly to make it not hold onto large objects in the collection at all.
I'm going to ignore the dictionary of Company objects. It's never used as a dictionary and is only used as a collection. It's also important to note that what's in the dictionary to begin with is not a complete company object. We had to use the original company object to retrieve the extra data. We usually call that a key. So instead of that dictionary, we'll have a stream of keys instead. Here's the key objects:
public class CompanyKey
{ }
And a data source to produce keys. This might actually be Smurfs dictionary, but for our purposes we'll make it an iterator method. That way nothing is rooting these things in memory. If the keys are small then it doesn't really matter but better to not use a collection if you don't need it.
public class CompanyKeySource
{
public IEnumerable<CompanyKey> GetKeys()
{
for(int i =0;i < 10;++i)
yield return new CompanyKey();
}
}
And here's the actual company object:
public class Company
{
public EmployeeData Employees { get; set; }
}
And the big glob of data. That's in the employee object.
public class Employee
{
public string[] LotOfData { get; set; }
}
Finally we need something that'll load the big glob of data into the company object. That's usually a repository of some type:
public class CompanyDataRepository
{
public IEnumerable<Company> GetCompanyDetails(IEnumerable<CompanyKey> keys)
{
foreach (var key in keys)
{
yield return new Company() { Employees = GetEmployees(key) };
}
}
public EmployeeData GetEmployees(CompanyKey key) =>
new EmployeeData() { LotOfData = new string[2500] };
}
Now we wire everything together and iterate over our company instances.
static void Main(string[] _)
{
CompanyDataRepository repository = new CompanyDataRepository();
CompanyKeySource keySource = new CompanyKeySource();
var keys = keySource.GetKeys();
foreach (var company in repository.GetCompanyDetails(keys))
{
// do whatever it is you're doing with your companies...
}
}
Now there's no need to pop items off a dictionary to keep them out of memory. The large chunks of data are used where they're needed and then can be eligible for collection right away.
foreach is using an enumerator concept. With the help of .MoveNext() method, it is observing items in the collection one after another with the help of .Current property. In other words, your code is translated to something like:
List<Compagny>.Enumerator enumerator = new List<Compagny>().GetEnumerator();
try
{
while (enumerator.MoveNext())
{
Compagny current = enumerator.Current;
LoadContacts(current);
current.Log = "Very large string...";
}
}
finally
{
((IDisposable)enumerator).Dispose();
}
As you see, each var compagny is in the end just a single local variable of reference type. This reference is being replaced in every iteration. So, from this point of view, current variable becomes an additional root for some Compagny object, for the time of the iteration.
The difference here may be what's the range of such a root. In case of the Debug build, it is indeed the whole {} block of a underlying while loop (in fact, the whole method, from the CIL perspective). In case of Release build, JIT is reporting roots more aggresively. So, it will report current as no longer needed, as fast as it is indeed no longer needed. In you case, you are using current till the end of the {} so it is the same.
The difference could be seen if you don't use compagny till the end of the block, like:
foreach (var compagny in compagnyList)
{
this.LoadContacts(compagny);
this.LoadNotes(compagny.ContactList)
compagny.Log = "Very large string...";
CallingHereLongLastingMethod(); // Debug - compagny is still a root
// Release - compagny is no longer a root
}
BUT... all this does not matter because current is just an additional object root. Object itself ("compagny") is still rooted by the compagnyList itself.
"would the GC be smart enough to understand that the company object would not be reused beyond the foreach iteration and mark it as candidate for garbage collection (despite the compagnyList that would remain be on the stack)?" question suggests you have some problems in understanding reference types and the GC fundamentals. List<> is a reference type, it contains of the reference (living "on the stack" as a local variable) and the data - collection of the companies.
Based on MikeJ's answer, I created a generic method that will loop thru a list and remove the reference to the element right after the iteration:
public static IEnumerable<T> PopEnumerable<T>(this List<T> list)
{
while (list.Count > 0)
{
yield return list[0];
list.RemoveAt(0);
}
}
That way you can still call the foreach loop...
foreach (Company company in companyList.PopEnumerable())
{
company.EmployeeList.AddRange(new Employee[25000]);
}
... but only the company of the current iteration will remain in memory. I did some benchmark and the memory allocation that use to reach 16 gb dropped to less than 100 mb for the whole process.

Querying object without a given name

I have created a list of objects using a C# line like this:
gameObjects.Add(new Object());
I have also made a function that prints on the screen a list of the object types contained within gameObjects.
for(int i = 0; i < gameObjects.Count; i++)
Console.WriteLine(gameObjects[i].GetType());
So far so good. However I'm getting more items printed on the screen than should be present in gameObjects, so I've been trying to work out a way of finding out whether any of the entries are duplicates as I can't find anything in my code that could be creating extra objects in the list. It would be great if I could print out the names of each object in the list, but as I haven't given them names I don't think this is possible. Is there anything else that would differentiate one object in the list from another that I could take advantage of? As it's just debugging, I didn't really want to have to go in and make sure each object is given a name.
Thanks!
Edit:
For those asking for more code, I have a function that adds objects of type staticObject to the gameObjects list:
private void CreateStaticObject(Vector2 v2StaticObjectPosition)
{
Texture2D staticObjectTexture = Content.Load<Texture2D>(#"textures\StaticObject");
GameInfo.gameInfo.gameObjects.Add(new StaticObject(staticObjectTexture, v2StaticObjectPosition, sbSpriteBatch));
}
The list is contained within a class called GameInfo. Each StaticObject inherits from a Sprite class, if that's of importance.
I also add a Player object to the list, which inherits from the StaticObject class:
private void CreatePlayer(Vector2 v2PlayerPosition)
{
Texture2D playerTexture = Content.Load<Texture2D>(#"textures\Player1");
player1 = new Player(playerTexture, v2PlayerPosition, sbSpriteBatch);
}
I'm then printing out the contents of the list with this:
for(int i = 0; i < GameInfo.gameInfo.gameObjects.Count; i++)
{
string sObjectString = string.Format("Game object {0} is a {1}", i, GameInfo.gameInfo.gameObjects[i].GetType());
DrawWithShadow(sObjectString, new Vector2(10, 20 * i + 10));
}
DrawWithShadow() is just a simple method which nicely formats the text on the screen in the desired location. Unfortunately though, for each object that I create by calling the CreateStaticObject() method, I end up with two entries in my list.
Updated because I was dumb, ReferenceEquals checks reference equality.
Have you tried Object.ReferenceEquals()? It will tell you if reference a and reference b point to the same object.
var x = new object();
var y = x;
//This will print "true"
Console.WriteLine(Object.ReferenceEquals(x,y));
If you just want to filter the dupes out of the list, try this:
gameObjects = gameObjects.Distinct().ToList();
You can use GroupBy to find all duplicate objects by using reference equality:
var duplicateGroups = gameObjects.GroupBy(obj => obj).Where(g => g.Count() > 1);
Note that this will use the object's Equals + GetHashCode methods if they are overridden.
Thanks for all the suggestions guys. However I've discovered the source of my problem. I had an old line of code in the constructor for the StaticObject class which adds any created StaticObject to the list. So I was adding each object twice. D'oh! :S

Initialize nested Object from a list

I have a list of integers (Levels). I want to initialize a nested Object of Filter called myFilter as below(Filter is a class with two properties: Value and NextFilter):
var myFilter = new Fliter{
Value = Levels[0],
NextFilter = new Filter{
Value = Levels[1],
NextFilter = new Filter{
Value = Levels[2],
NextFilter = new Filter{
Value = Levels[n],
NextFilter = null
}
}
}
}
Level's count is not static and depends on the input list (I have a multi select list that generates Level)
How can I do that?
This is a classic event for using - the technique of a method that calls itself:
public static Filter CreateFilter(List<int> values) => values.Any() ? new Filter //If the list contains elements, create the filter
{
Value = values.First(), //assign the first item of the values to the value property
NextFilter = CreateFilter(values.Skip(1).ToList()) //Create the rest of the nested object with the rest of the values
} : null; //If there aren't any items left in the list, return null and stop the recursion
You could of course do it in the constructor as well:
public Filter(List<int> values)
{
if (!values.Any()) return;
Value = values.First();
NextFilter = values.Count > 1 ? new Filter(values.Skip(1).ToList()) : null;
}
For more information about recursion, take a look at this: https://www.dotnetperls.com/recursion, for more information on nested classes read through this: https://www.dotnetperls.com/nested-class.
A few more information on recursion:
You can actually achieve everything through recursion - you don't even need loops. That's the reason why in languages like Haskell loops don't exist.
The simplest recursive function is:
public static void EndlessLoop()
{
//Loop body
EndlessLoop();
}
However, even Resharper suggests to convert it to a loop:
Another example, if you want to get the sum of a list you could do:
public static int Sum(List<int> summands) => summands.Count > 0
? summands.First() + Sum(summands.Skip(1).ToList())
: 0;
But those examples aren't useful in C#, as C# isn't a functional programming language, which causes recursion to be slower than loops. Furthermore recursion often causes a StackOverflowException (fitting to this site). If you run the endless loop recursion, it doesn't even take a second till your stack is full.
The reason for this is, that C# adds the address, from which a method got called, to the stack. If a method is called very often (and in 1 second a lot of recursive calls are made) a lot of addresses are added to the stack, so that it overflows.
However I still think, even though those examples aren't useful in c#, that it's quite useful to be able to handle recursion. Recursion is for example the only way to explore a directory structure, for getting for example all files:
public static List<FileInfo> GetAllFiles(DirectoryInfo directory) => directory.GetFiles()
.Concat(directory.GetDirectories().SelectMany(GetAllFiles))
.ToList();
And, as you experienced, it's the only way to fill a nested class from a list properly.
Just make a constructor of Filter, that will get Levels array as a parameter, that will set it's Value as level[0], and init NextFilter = new Filter(level.Skip(1)). Something like that. And it will recursively initialize your object.

Should this list initializer behavior be reported as bug in the Visual Studio C# Compiler?

First of all I will say that I've changed my design and no longer need that but getting a good answer for that will still be nice
I have the following class, ListContainer, in my code (The attached codes are all mcve):
class ListContainer
{
public object ContainedList
{
get; private set;
}
public int Value
{
get; private set;
}
public ListContainer(object list, int value)
{
ContainedList = list;
Value = value;
}
}
And in some other class in my code I have a List<ListContainer> and I need each ListContainer to contain this List<ListContainer>, so I can implement it like that:
//Field in the class
List<ListContainer> mContainers = null;
//In the constructor:
mContainers = new List<ListContainer>();
mContainers.Add(new ListContainer(mContainers, SOME_CONST));
mContainers.Add(new ListContainer(mContainers, SOME_OTHER_CONST));
Than it works fine, but when I've tried to use list initializer:
//Field in the class
List<ListContainer> mContainers = null;
//In the constructor:
mContainers = new List<ListContainer>
{
new ListContainer(mContainers, SOME_CONST),
new ListContainer(mContainers, SOME_OTHER_CONST)
}
You would expect the results to be equivalent but in reality the result looks like that:
mContainers
[0] - ListContainer
ContainedList = null
Value = SOME_CONST
[1] - ListContainer
ContainedList = null
Value = SOME_OTHER_CONST
Seeing this results I've inspected the output MSIL of this C# compilation and seen the following code:
Now, this explains why the problem occurs, and I've even checked out in the CSharp Language Specification document and this is the defined behavior:
A List can be created and initialized as follows:
var contacts = new List<Contact> {
new Contact {
Name = "Chris Smith",
PhoneNumbers = { "206-555-0101", "425-882-8080" }
},
new Contact {
Name = "Bob Harris",
PhoneNumbers = { "650-555-0199" }
}
};
which has the same effect as
var __clist = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
var contacts = __clist;
where __clist, __c1 and __c2 are temporary variables that are otherwise invisible and inaccessible.
So obviously this behaviour is intended. Is there a good reason everything is done on the temporary variable and not on the original one? since it seems like a wrong behaviour to me.
The reason is to avoid race conditions with concurrent threads accessing the original variable where you would add your elements. An inconsistency would appear if a thread accesses the variable while not all elements are added to it yet.
Two threads accessing the same variable would therefore get an inconsistent list, with different elements in it.
This would not come as a shock if the elements are added on different lines, but since you use an object initializer, it is normal to perceive the object as directly initialized with all its elements in it, hence the need of a temporary, invisible, variable.
Is there a good reason everything is done on the temporary List and not on the original one?
There is no original list:
var __clist = new List<Contact>();
// …
__clist.Add(__c1);
// …
__clist.Add(__c2);
var contacts = __clist;
Only one list is ever created. What you probably mean is that it’s done on a temporary variable instead of the original variable, but that has no practical effect—other than probably being easier to implement. This is especially true if you think about that collection initialization is not limited to the context of variable assignments:
SomeMethodCall(new List<int>() { 1, 2, 3 });
Since there is no reference to that list, the simplest solution to implement this is just to use a temporary variable that holds the list, and put that variable at the place of the initializer then.
What’s also important about this is that the old value is completely overwritten. So in your mContainers = new List<ListContainer>, the old value of mContainers is never being looked at for the purpose of the initializer syntax.
It’s probably a good idea to think about the collection initialization as an “atomic” operation. The list only exists (at least to you) once the whole initializer completes. So you cannot reference itself from within the initializer.
Assignments are first evaluated on the right side of the = and then the assignment takes place. So mContainers is still null.

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