LINQ Replacement for Nested Array Iteration and Update? - c#

I need some help with syntactic sugar.
I have a ThisClass[3] and ThatClass[3].
public class ThisClass
{
public string Thing1;
public string Thing2;
public string Thing3;
public string Thing4;
}
public class ThatClass
{
public string Thing1;
public string Thing2;
}
Each instance in the array of ThatClass was created based on an instance in the same position of array ThisClass.
So ThatClass[0] has its fields with the same values as ThisClass[0], except it only has 2 fields instead of 4.
I would like to now update each instance in the ThisClass array, with fields from the matching index position of the object in the ThatClass array. I could do nested for loops, but I need help thinking through a LINQ option.
ThisClass[0].Thing1 = ThatClass[0].Thing1;
ThisClass[0].Thing2 = ThatClass[0].Thing2;
works but I am sure could be done better. Using C#, .NET 4.5.

I don't see any need for nested loops:
for (int i = 0; i < theseClasses.Length; i++)
{
theseClasses[i].Thing1 = thoseClasses[i].Thing1;
theseClasses[i].Thing2 = thoseClasses[i].Thing2;
}
You could potentially add a CopyFrom(ThatClass) method to ThisClass, leading to:
for (int i = 0; i < theseClasses.Length; i++)
{
theseClasses[i].CopyFrom(thoseClasses[i]);
}
... but that's all I'd do. LINQ is do to with querying, not causing side-effects... I don't think it's a good fit here.

Attention: As #Jon put, LINQ is not about causing side-effects and if you do so you may end up with a code with unexpected behavior (but it's possible).
This code does that:
ThisClass[] these = new ThisClass[100];
ThatClass[] those = new ThatClass[100];
// init these and those items
those.Zip(these, (that, #this) =>
{
#this.Thing1 = that.Thing1;
#this.Thing2 = that.Thing2;
return that;
}).ToList();

As you're asking for LINQ... this will get you an unrelated IEnumerable<ThisClass>, and will not modify the original array.
(I'm assuming that the thisClass and thatClass arrays are called thisArray and thatArray, respectively)
thisArray.Select((n, x) => { n.Thing1 = thatArray[x].Thing1; n.Thing2 = thatArray[x].Thing2; return n; }).ToArray();
(If you really wanted LINQ and assigning it, just assign it back to the original array)

Related

Get data from arraylist of Structs, then inserting more data into other elements of the struct

I have an Arraylist of Structs called tag (shown below) and I can successfully import data into the structure. (The data shows up correctly when I debug.)
How can I access the data and write it to other elements in the structure?
struct tag
{
public string name;
public string type;
public string rack;
public string card;
public string channel;
public string data;
public string fault;
};
ArrayList TagList = new ArrayList();
importTag.name = "aName";
importTag.type = "atype";
importTag.rack = "arack";
importTag.card = "acard";
importTag.channel "achannel";
TagList.Add(importTag);
foreach (tag t in TagList){
//tasty stuff in here
//
}
You shouldn't use a struct here in the first place. To being with, mutable structs are evil. When you have a mutable struct you end up mutating a copy of the value instead of the value itself, so the code looks sensible, but doesn't actually work. This is why structs should virtually always be immutable.
Next, your struct doesn't represent a single value; if you're making a struct, it should represent a singular logical value.
You also shouldn't use a struct if it's large; your struct is quite large. This means all of that data is constantly being copied around.
You also shouldn't use a struct when it'll be boxed often, and you're boxing the structs that you're using.
You should use a class instead. If you change your code to use a class rather than a struct, and also use a List instead of an ArrayList to leverage generics, then after that your code can become:
foreach(tag t in tagList)
{
t.name = "new name";
}
If you insist on using a struct despite the fact that it violates all of the guidelines for when to use a struct then you'll need to create a new struct based on the new and old values and assign that new struct to the list:
for(int i = 0; i < tagList.Count; i++)
{
tagList[i] = new tag
{
name = "new name",
type = tagList[i].type,
rack = tagList[i].rack,
card = tagList[i].card,
channel = tagList[i].channel,
data = tagList[i].data,
fault = tagList[i].fault,
};
}
If you are creating collection of same data then use strictly typed collections like List
List<tag> tagList = new List<tag>();
And because you are using ArrayList it returns each 'tag' as object so you need to do casting but if you are using above approach you can access list elements like this :
foreach(tag t in tagList)
{
t.name = "your data";
}
Hope this helps.
Avoid using ArrayList with structs. If you use List<tag> rather than ArrayList, then you can modify items in the struct using:
List<tag> myList;
for (int i=0; i < myList.Count; i++)
{
var temp = myList[i];
temp.card = "New value";
myList[i] = temp;
}
If you don't mind using an array and managing the size yourself, then you can do something like:
tag[] myTags = new tag[16];
int myTagsCount;
void AddTag(tag newTag)
{
if (myTagsCount >= myTags.Length)
Array.Resize(ref myTags, myTagsCount*2);
myTags[myTagsCount++] = newTag;
}
... and then when you want to modify each tag:
for (int i=0; i < myTags.Length; i++)
{
myTags[i].card = "New Value"; // Arrays allow efficient in-place modification of structures
}
Note that if you use mutable class instead of a structure, some operations will be more convenient but you need to make sure that you never store into any collection which is supposed to hold tag values any reference to a tag which might be modified elsewhere. Immutable classes will make sharing data a little easier and more efficient, but the only way to change what's encapsulated within a field or collection member will be to replace it with an entirely new object instance. Structures often represent a very useful compromise.
I'm not sure if I understood you correctly but, do you what to do this?
foreach (tag t in TagList){
t.name = "My New Name";
}

Convert an object array of object arrays to a two dimensional array of object

I have a third party library returning an object array of object arrays that I can stuff into an object[]:
object[] arr = myLib.GetData(...);
The resulting array consists of object[] entries, so you can think of the return value as some kind of recordset with the outer array representing the rows and the inner arrays containing the field values where some fields might not be filled (a jagged array). To access the individual fields I have to cast like:
int i = (int) ((object[])arr[row])[col];//access a field containing an int
Now as I'm lazy I want to access the elements like this:
int i = (int) arr[row][col];
To do this I use the following Linq query:
object[] result = myLib.GetData(...);
object[][] arr = result.Select(o => (object[])o ).ToArray();
I tried using a simple cast like object[][] arr = (object[][])result; but that fails with a runtime error.
Now, my questions:
Is there a simpler way of doing this? I have the feeling that some
nifty cast should do the trick?
Also I am worried about performance
as I have to reshape a lot of data just to save me some casting, so I
wonder if this is really worth it?
EDIT:
Thank you all for the speedy answers.
#James: I like your answer wrapping up the culprit in a new class, but the drawback is that I always have to do the Linq wrapping when taking in the source array and the indexer needs both row and col values int i = (int) arr[row, col]; (I need to get a complete row as well like object[] row = arr[row];, sorry didn't post that in the beginning).
#Sergiu Mindras: Like James, i feel the extension method a bit dangerous as it would apply to all object[] variables.
#Nair: I chose your answer for my implementation, as it does not need using the Linq wrapper and I can access both individual fields using int i = (int) arr[row][col]; or an entire row using object[] row = arr[row];
#quetzalcoatl and #Abe Heidebrecht: Thanks for the hints on Cast<>().
Conclusion: I wish I could choose both James' and Nair's answer, but as I stated above, Nair's solution gives me (I think) the best flexibility and performance.
I added a function that will 'flatten' the internal array using the above Linq statement because I have other functions that need to be fed with such a structure.
Here is how I (roughly) implemented it (taken from Nair's solution:
public class CustomArray
{
private object[] data;
public CustomArray(object[] arr)
{
data = arr;
}
//get a row of the data
public object[] this[int index]
{ get { return (object[]) data[index]; } }
//get a field from the data
public object this[int row, int col]
{ get { return ((object[])data[row])[col]; } }
//get the array as 'real' 2D - Array
public object[][] Data2D()
{//this could be cached in case it is accessed more than once
return data.Select(o => (object[])o ).ToArray()
}
static void Main()
{
var ca = new CustomArray(new object[] {
new object[] {1,2,3,4,5 },
new object[] {1,2,3,4 },
new object[] {1,2 } });
var row = ca[1]; //gets a full row
int i = (int) ca[2,1]; //gets a field
int j = (int) ca[2][1]; //gets me the same field
object[][] arr = ca.Data2D(); //gets the complete array as 2D-array
}
}
So - again - thank you all! It always is a real pleasure and enlightenment to use this site.
You could create a wrapper class to hide the ugly casting e.g.
public class DataWrapper
{
private readonly object[][] data;
public DataWrapper(object[] data)
{
this.data = data.Select(o => (object[])o ).ToArray();
}
public object this[int row, int col]
{
get { return this.data[row][col]; }
}
}
Usage
var data = new DataWrapper(myLib.GetData(...));
int i = (int)data[row, col];
There is also the opportunity to make the wrapper generic e.g. DataWrapper<int>, however, I wasn't sure if your data collection would be all of the same type, returning object keeps it generic enough for you to decide what data type cast is needed.
There are few similar answer posted which does something similar. This differ only if you want to acess like
int i = (int) arr[row][col];
To demonstrate the idea
public class CustomArray
{
private object[] _arr;
public CustomArray(object[] arr)
{
_arr = arr;
}
public object[] this[int index]
{
get
{
// This indexer is very simple, and just returns or sets
// the corresponding element from the internal array.
return (object[]) _arr[index];
}
}
static void Main()
{
var c = new CustomArray(new object[] { new object[] {1,2,3,4,5 }, new object[] {1,2,3,4 }, new object[] {1,2 } });
var a =(int) c[1][2]; //here a will be 4 as you asked.
}
}
(1) This probably could be done in short and easy form with dynamic keyword, but you'll use compile-time checking. But considering that you use object[], that's a small price:
dynamic results = obj.GetData();
object something = results[0][1];
I've not checked it with a compiler though.
(2) instead of Select(o => (type)o) there's a dedicated Cast<> function:
var tmp = items.Select(o => (object[])o).ToArray();
var tmp = items.Cast<object[]>().ToArray();
They are almost the same. I'd guess that Cast is a bit faster, but again, I've not checked that.
(3) Yes, reshaping in that way will affect the performance somewhat, depending mostly on the amount of items. The impact will be the larger the more elements you have. That's mostly related to .ToArray as it will enumerate all the items and it will make an additional array. Consider this:
var results = ((object[])obj.GetData()).Cast<object[]>();
The 'results' here are of type IEnumerable<object[]> and the difference is that it will be enumerated lazily, so the extra iteration over all elements is gone, the temporary extra array is gone, and also the overhead is minimal - similar to manual casting of every element, which you'd do anyways.. But - you lose the ability to index over the topmost array. You can loop/foreach over it, but you cannot index/[123] it.
EDIT:
The James's wrapper way is probably the best in terms of overall performance. I like it the most for readability, but that's personal opinion. Others may like LINQ more. But, I like it. I'd suggest James' wrapper.
You could use extension method:
static int getValue(this object[] arr, int col, int row)
{
return (int) ((object[])arr[row])[col];
}
And retrieve by
int requestedValue = arr.getValue(col, row);
No idea for arr[int x][int y] syntax.
EDIT
Thanks James for your observation
You can use a nullable int so you don't get an exception when casting.
So, the method will become:
static int? getIntValue(this object[] arr, int col, int row)
{
try
{
int? returnVal = ((object[])arr[row])[col] as int;
return returnVal;
}
catch(){ return null; }
}
And can be retrieved by
int? requestedValue = arr.getIntValue(col, row);
This way you get a nullable object and all encountered exceptions force return null
You can use LINQ Cast operator instead of Select...
object[][] arr = result.Cast<object[]>().ToArray()
This is a little less verbose, but should be nearly identical performance wise. Another way is to do it manually:
object[][] arr = new object[result.Length][];
for (int i = 0; i < arr.Length; ++i)
arr[i] = (object[])result[i];

How can I top up the number of elements in a list to make it equal to ten?

I have a City class and inside that a Detail class:
public class City {
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string Notes { get; set; }
public class Detail
{
public Detail()
{
ImageFile = String.Empty;
Explanation = new HtmlText();
}
public string ImageFile { get; set; }
public HtmlText Explanation { get; set; }
}
}
In my code I have some lines that check how many details there are and if there are less than ten then I add new City.Details. I am using the code below to do this but it's located in a few different methods and does not look clean. Is there some way that I could simplify this and also add the logic of checking, counting and adding into my base City class?
foreach (int index in Enumerable.Range(0, 10 - vm.Details.Count()))
{
vm.Details.Add(new City.Detail());
}
You could add a MinReached and a FillDetails method to your City class.
The first one checks if you already reached the minimum, the second add new details up to ten.
public bool MinReached()
{
return this.Details.Count >= 10;
}
public void FillDetails()
{
for (int i = Details.Count; i <= 10; i++)
this.Add(new City.Detail());
}
If you always have the need to have to 10 Details to be made available can do something on these lines
Enumerable.Range(0, count).Select(i => new City.Detail()).ToList();
This is for 10 Details exclusively or if you want to add the remainders then calculate the difference then use .Concat() to append it on to the existing list.
As the others said, use something like the following
while(vm.Details.Count() < 10)
vm.Details.Add(new City.Detail());
or even a regular for construct
for(int x = vm.Details.Count(); x < 10; x++)
vm.Details.Add(new City.Detail());
Otherwise when other people read your code (or you look at it 3 months from now) the reaction is going to be "huh?" instead of just automatically recognizing what's happening.
Here are three ways to address the problem:
1) Just add ten Details to the City object when you create it, use those and then create more if/when necessary
2) Why do you need 10 Details if there aren't really 10 Details? As much as possible it's best to have your objects truly represent what they are... representing. So perhaps you are trying to fix what is just a symptom of a deeper problem. But if that's not the case, then...
3) As the others have mentioned just move this logic into your base class.
Edit: I should have also made clear on #3 that you need a way to make this process automatic so you don't have to explicitly call the procedure that pads it with extra Details. I don't have enough info from your code to know how exactly to tell you to do this but if you want to provide more info as to why it's important to have 10 Details then I'm sure I could help further.
Why can't you use for loop
for (int i = 0; i < 10 - vm.Details.Count(); i++) vm.Details.Add(new City.Detail());
I'm not sure what type your cm variable is but if you want to add something to your City class you could add a static method to the City class named something like SetDetailSize that accepts a collection of Detail objects and a size you want to force the collection to be and then just call City.SetDetailSize method passing in your collection.
"..do this using LINQ that would eliminate the need for the for loop altogether".
Surely you could do it with linq but that does not mean the looping would not take place. Use ForEach linq operator. BUt this code is similar to what you had written except its in linq style
LINQ Code
Enumerable.Range(0, 10-vm.Details.Count()).ToList().ForEach(counter=>vm.Details.Add(new City.Detail());
EDIT
Think this would solve your problem. As soon as your class instansiate , fill 10 cities by default. Use a counter for checking how many dummy elements are there in. Once user add a detail, replace the dummy with user's input. Put this in your base class and then you can forget about adding additional details at every other place
public class City
{
int counter = 0;
public City()
{
//fill 10 elements by default
Enumerable.Range(0, 10).ToList().ForEach(counter =>vm.Details.Add(new City.Detail());
}
.
.
}
//Now define your add method as following
public void AddDetails(Details d)
{
//remove the dummy element
vm.Details.RemoveAt(counter);
//add original element and increase the counter, so next element would be added at next index
vm.Details.insert(counter++, d);
}
Another option is to create an extension method for the List collection. This would allow you to call a method on a List of Detail objects from anywhere within your application. Do the following:
public static class ExtensionMethods
{
public static void SetDetailSize(this List<City.Detail> details, int size)
{
for (int i = 0; i < size - details.Count; i++)
details.Add(new City.Detail());
}
}
This solves the duplication of code problem because anywhere you have a List of City.Detail objects, you can just make the following call.
vm.Details.SetDetailSize(10);
Make sure you have a using statement that references the namespace for the ExtensionMethods class.

c# looping object creation

I'm very new with c#, and was previously attempting to ignore classes and build my small program structurally more similar to PHP. After reaching a road block, I'm trying to start over and approach the problem properly OO. I'm taking a long file, and in a loop, every time certain conditions are met, I want to make a new object. How can I have it create a new object, without having to specify a unique name?
Referral ObjectName = new Referral(string, string, int);
Secondly, once this is done, and the strings & int set their appropriate object properties, how can i unique-ify the class by one property, and then sort the class by another?
I'm sorry if these are basic questions, I have spent a large, large amount of time first trying to figure it out on my own with google, and a textbook. If only C# would allow multi-dimensional arrays with different types!
Thank you so much!
PS. I do mean to extract a list of unique objects.
All these answers, while helpful, seem to involve creating a shadow set of IEnumerables. Is there no way to do this with the class itself?
Trying the first solution, provided by Earwicker, adding each object to a List from within the loop, when I try to Write a property of the element to the console, i get the ClassName+Referral. What could I be doing wrong?--solved. still needed .property
still working. . .
C# does allow untyped arrays. All objects are derived ultimately from object, so you use an array or container of objects. But it's rarely necessary. How many types of object do you have?
Within the loop block, you can create an object exactly as you do in that line of code (except with the syntax fixed), and it will be a new object each time around the loop. To keep all the objects available outside the loop, you would add it to a container:
List<Referral> referrals = new List<Referral>();
// in the loop:
Referral r = new Referral(str1, str2, num1);
referrals.Add(r);
Suppose Referral has a numeric property called Cost.
referrals.Sort((l, r) => l.Cost - r.Cost);
That sorts by the cost.
For ensuring uniqueness by some key, you may find it easier to pick a more suitable container.
Dictionary<string, Referral> referrals = new List<Referral>();
// in the loop:
Referral r = new Referral(str1, str2, num1);
referrals[str1] = r;
This stores the referral in a "slot" named after the value of str1. Duplicates will overwrite each other silently.
First, you're going to need to spend some time familiarizing yourself with the basics of the language to be productive. I recommend you take a little time to read up on C# before getting in too deep - otherwise you'll spend a lot of your time spinning your wheels - or reinventing them :)
But here's some info to get you started.
Typically, in C# you create classes to represent elements of your program - including those that are used to represent information (data) that your program intends to manipulate. You should really consider using one, as it will make data manipulation clearer and more manageable. I would advise avoiding untyped, multi-dimensions array structures as some may suggest, as these rapidly become very difficult to work with.
You can easily create a Referall class in C# using automatic properties and a simple constructor:
public class Referall
{
// these should be named in line with what they represent...
public string FirstString { get; set; }
public string AnotherString { get; set; }
public int SomeValue { get; set; }
public Referall( string first, string another, int value )
{
FirstString = first;
AnotherString = another;
SomeValue = value;
}
}
You can add these to a dictionary as you create them - the dictionary can be keyed by which ever property is unique. Dictionaries allow you to store objects based on a unique key:
Dictionary<string,Referall> dict = new Dictionary<string,Referall>();
As you process items, you can add them to the dictionary:
Referall ref = new Referall( v1, v2, v3 );
// add to the dictionary, keying on FirstString...
dict.Add( ref.FirstString, ref );
If you need to sort items in the dictionary when you're done, you can use LINQ in C# 3.0:
IEnumerable<Referall> sortedResults =
dict.Values.OrderBy( x => x.AnotherString );
You can sort by multiple dimension using ThenBy() as well:
IEnumerable<Referall> sortedResults =
dict.Values.OrderBy( x => x.AnotherString )
.ThenBy( x => x.SomeValue );
List<Referral> referrals = new List<Referral>();
for (...)
{
referrals.Add(new Referral(string1, string2, number1));
}
Then, if you're using Linq (which I highly suggest), you can do this:
IEnumerable<Referral> sorted = referrals.OrderBy(x => x.string1).ThenBy(x => x.string2);
Otherwise, you can use the Sort() method on List<Referral>.
You can create an object without a reference, but you won't have any access to it later:
new Referral(string, string, int);
If you wish to put them in an array/list, these different types need to have a common base class. This is called polimorfism, which is a very important concept in OO programming.
You cannot ignore classes while using c#. Don't resist the change!
Do you really not need to create a class here? Do you really not need to give it a name? C# does allow loose typing, but type safety is a good thing.
I don't fully understand what you're trying to do. But maybe LINQ is what you're looking for. There's tons of documentation around, but as a quick 'teaser' have a look at the 101 Linq samples on MSDN
C# includes a wonderful feature called "iterator blocks". What you want to do is use the yield keyword to create an Enumerable of your Referal object, something like this (not that I'm making the file format and property names up, because you didn't share that):
public class Referral
{
public Guid id { get; private set; } // "uniquify"
public int ReferringId { get; set; }
public string ReferrerText { get; set; }
public string ReferrerDescription { get; set; }
private Referral()
{
id = new Guid();
}
private Referral(string Text, string Description, int ReferringId) : this()
{
this.ReferrerText = Text;
this.ReferrerDescription = Description;
this.ReferringId = ReferringId;
}
public static IEnumerable<Referral> GetReferrals(string fileName)
{
using (var rdr = new StreamReader(fileName))
{
var next = new Referrer();
int state = 0;
string line;
while ( (line = rdr.ReadLine() ) != null)
{
switch (state)
{
case 0:
next.ReferrerText = line;
state = 1;
break;
case 1:
next.ReferrerDescription = line;
state = 2;
break;
case 2:
next.ReferringId = int.Parse(line);
yield return next;
next = new Referral();
state = 0;
break;
}
}
}
}
}
Now you want to sort the referrals and presumable enumerate over them for some purpose. You can do that easily like this:
foreach (var referral in Referral.GetReferrals(#"C:\referralfile.txt").OrderBy( r => r.Text ) )
{
OutputReferral(referral);
}

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