I want the code to show how many wagons there are and which animals are in each wagon. This is my error:
System.InvalidOperationException: "The collection has been changed. The inventory processing may not be performed. "
This is the code:
public IEnumerable<Animal> GetAnimals()
{
return Animals.AsEnumerable();
}
public void Checker(List<Animal> listAnimals)
{
foreach (Animal animal in listAnimals)
{
foreach (Wagon wagon in Wagons)
{
foreach (Animal wagonAnimal in wagon.GetAnimals())
{
if (wagon.StartCapacity <= wagon.MaxCapacity &&
animal.Formaat + wagon.StartCapacity <= wagon.MaxCapacity &&
wagonAnimal.Eater == Eater.carnivoor &&
animal.Eater == Eater.herbivoor &&
animal.Formaat >= wagonAnimal.Formaat)
{
wagon.AddAnimal(animal);
Wagons.Add(wagon);
}
else
{
Wagon waggi = new Wagon();
waggi.AddAnimal(animal);
Wagons.Add(waggi);
}
}
}
Wagon wag = new Wagon();
wag.AddAnimal(animal);
Wagons.Add(wag);
}
}
Can anyone give me some hints on this issue?
If you are looking to modify the collection while looping I would use a List object instead of an IEnumerable.
Some sample code would be like this:
List<Wagons> Wagons = new List<Wagons>
Wagons.AddAnimal(animal1);
foreach(Animal animal in Wagons.GetAnimals(){
animal.Eater = Eater.herbivore;
}
Also looking at your code:
if (wagon.StartCapacity <= wagon.MaxCapacity &&
animal.Formaat + wagon.StartCapacity <=
wagon.MaxCapacity && wagonAnimal.Eater == Eater.carnivoor &&
animal.Eater == Eater.herbivoor && animal.Formaat >= wagonAnimal.Formaat)
{
wagon.AddAnimal(animal);
Wagons.Add(wagon);
} else {
wagon.AddAnimal(animal);
Wagons.Add(wagon);
}
This if/else statement does the exact same code, so you really don't need an if/else, you can just add the animal and add the wagon.
Lastly, shouldn't the parameter to your method accept a List or IEnumerable collection of wagons and not animals so you can loop through the wagons, and look through the animals in the wagons?
You cannot modify a list while iterating over it using foreach and in.
Example:
foreach (Wagon wagon in Wagons)
{
Wagon waggi = new Wagon();
Wagons.Add(waggi);
}
will not work.
If you use e.g.
// This is needed to not get an endless loop (Because the length of the list
// increases after each time the Add() method is called to Wagons.)
int wagonCount = Wagons.Count;
for (int i = 0; i < wagonCount ; i++)
{
Wagon waggi = new Wagon();
waggi.AddAnimal(animal);
Wagons.Add(waggi);
}
this will work.
My working example for your code (as far as I could get what you want to do is here:
https://dotnetfiddle.net/6HXYmI and here: https://gist.github.com/SeppPenner/a082062d3ce2d5b8196bbf4618319044.
I would also recommend to check your code style according to the definitions from Microsoft: https://learn.microsoft.com/en-US/dotnet/csharp/programming-guide/inside-a-program/coding-conventions.
Actually you can't modify the list while looping.
You need to create another object and add the wagon and animals respectively.
Try this and comment if you didn't understand yet
Related
I want to iterate over a custom list i.e. defined as:
List<CurrentCluster> _curClusters = new List<CurrentCluster>();
IEnumerator<CurrentCluster> _clusIterate = _curClusters.GetEnumerator();
while (_clusIterate.MoveNext())
{
// Error_01: Cannot implicitly convert CurrentCluster to Cluster
Cluster _curClus = _clusIterate.Current; // Cluster is base class while
// CurrentCluster is derived class
// Error_02: Does not contain a definition for GetClusterSize()
if (_curClus.GetClusterSize() == 0)
{
// Error_03: Remove(char) has some invalid arguments.
_clusIterate.ToString().ToList().Remove(_curClus);
}
}
while method GetClusterSize() is defined in class Cluster.cs as:
public int GetClusterSize()
{
return _clusterObjects.Count;
// _clusterObjects is a defined in this class as:
// List<EvoObject> _clusterObjects = new List<EvoObject>();
}
If the size of specific cluster is equal to zero in that cluster list (i.e. _curClusters then to remove that cluster from the list.
How can we iterate over a custom list and remove item from list conditionally?
How about just using List RemoveAll method and doing this?
_curClusters.RemoveAll(_curClus=>_curClus.GetClusterSize() == 0);
You should be able to use a for loop - you have to work backwards because otherwise you would be moving the elements and some would get skipped.
for (int n=_curClusters.Count; n>=0; n--)
{
if (_curClusters[n].GetClusterSize()==0)
{
_curClusters.RemoveAt(n);
}
}
Removing items from a collection using iteration is both advanced and obsolete technique. Use LINQ instead:
_curClusters = _curClusters.Where(c => c.GetClusterSize() > 0).ToList();
Now curClusters contains just "sized clusters", whatever that means.
If you insist to do it through iterations this is the way:
The catch is that you MUST NOT change a collection while iterating over its items. Instead, you can iterate and determine if an item needs to be deleted and mark it somehow - for instance you can add it to another list which contains only items to be deleted. After the first iteration over the original collection, start second one and remove the items from the original, like so:
var toBeRemoved = new List<CurrentCluster>();
foreach (var suspiciousCluster in _curCluseters)
{
if(suspiciousCluster.GetClusterSize() == 0)
{
toBeRemoved.Add(suspiciousCluster);
}
}
foreach (var voidCluser in toBeRemoved)
{
_curCluster.Remove(voidCluster);
}
Again, _curClusters contains just "sized clusters", whatever this might mean.
However I highly recommend the first approach.
I did not understand why you are going with that complexity ... simply you can achieve the goal by below code
List<CurrentCluster> _curClusters = new List<CurrentCluster>();
_curClusters.RemoveAll(i => i.GetClusterSize()== 0);
//OR
for (int i = 0; i < _curClusters.Count; )
{
//If you have some more logical checking with CurrentCluster
//before remove
if (_curClusters[i].GetClusterSize()== 0)
{
_curClusters.Remove(_curClusters[i]);
continue;
}
i++;
}
I have a class called Estimate and it has the following field and property:
private IList<RouteInformation> _routeMatrix;
public virtual IList<RouteInformation> RouteMatrix
{
get
{
if (_routeMatrix != null && _routeMatrix.Count > 0)
{
var routeMatrix = _routeMatrix.ToList();
routeMatrix =
routeMatrix.OrderBy(tm => tm.Level.LevelType).ThenBy(tm => tm.Level.LevelValue).ToList();
return routeMatrix;
}
else return _routeMatrix;
}
set { _routeMatrix = value; }
}
So, in the getter method, I am just sorting the _routeMatrix by Level Type and then by Level Value and returning the sorted list.
In one of my programs, I have the following code:
public void SaveApprovers(string[] approvers)
{
int i = 1;
foreach (var approver in approvers)
{
var role = Repository.Get<Role>(long.Parse(approver));
var level = new Models.Level
{
LevelType = LevelType.Approver,
LevelValue = (LevelValue)i,
Role = role
};
Repository.Save(level);
var routeInformation = new Models.RouteInformation
{
Level = level,
RouteObjectType = RouteObjectType.Estimate,
RouteObjectId = _estimate.Id
};
Repository.Save(routeInformation);
_estimate.RouteMatrix.Add(routeInformation); // <--- The problem is here
Repository.Save(_estimate);
i++;
}
}
The problem is that, if there are multiple approvers (i.e: the length of the approvers array is greater than 1, only the first routeInformation is added in the RouteMatrix. I don't know what happen to the rest of them, but the Add method doesn't give any error.
Earlier, RouteMatrix was a public field. This problem started occuring after I made it private and encapsulated it in a public property.
Your get member returns a different list, you add to that temporary list.
get
{
if (_routeMatrix != null && _routeMatrix.Count > 0)
{
var routeMatrix = _routeMatrix.ToList(); // ToList creates a _copy_ of the list
...
return routeMatrix;
}
else return _routeMatrix;
}
.....
_estimate.RouteMatrix.Add(routeInformation); // add to the result of ToList()
I think the moral here is not to make getters too complicated. The sorting is wasted effort anyway when you just want to Add().
Also, bad things will happen when _routeMatrix == null. That may not happen but then the if (_routeMatrix != null && ...) part is misleading noise.
When you are applying ToList() then completely new list is created, which is not related to original _routeMatrix list. Well, they share same elements, but when you add or remove elements from one of lists, it does not affect second list.
From MSDN:
You can append this method to your query in order to obtain a cached
copy of the query results.
So, you have cached copy of your _routeMatrix which you are successfully modifying.
To solve this issue you can return IEnumerable instead of IList (to disable collection modifications outside of estimation class), and create AddRouteInformation method to estimation class which will add route information to _routeMatrix. Use that method to add new items:
_estimate.AddRouteInformation(routeInformation);
Repository.Save(_estimate);
The problem is that you're not actually modifying _routeMatrix, you're modifying a copy of it. Don't issue the ToList on _routeMatrix, just sort it. Change the get to this:
get
{
if (_routeMatrix != null && _routeMatrix.Count > 0)
{
_routeMatrix =
_routeMatrix.OrderBy(tm => tm.Level.LevelType).ThenBy(tm => tm.Level.LevelValue).ToList();
return _routeMatrix;
}
else return _routeMatrix;
}
I need to iterate over a list (or whatever enumeration), but I'd like to add values into the list in the course of the iteration.
This is an example.
public static void RunSnippet()
{
List<int> hello = new List<int>();
hello.Add(1); hello.Add(2); hello.Add(3);
foreach (var x in hello)
{
Console.WriteLine(x);
if (x == 1) {
hello.Add(100);
}
}
}
I expect to get "1,2,3,100", but instead I got this error.
How can I iterate over a list that is changing in the process?
ADDED
What I want to accomplish is that I iterate over elements to process something. The thing is that some of the elements needs to be decomposed into sub elements on and on.
public static void RunSnippet()
{
List<Element> hello = new List<Element>();
hello.Add(Element); hello.Add(Element); hello.Add(Element);
foreach (var x in hello)
{
List<Element> decomposed;
decomposed = Decompose(x);
if (decomposed != null) {
foreach (var y in decomposed)
{
hello.Add(y);
}
}
}
}
You can't, basically. Not with a foreach loop, anyway. You can use a straight for loop:
for (int i = 0; i < hello.Count; i++)
{
int x = hello[i];
Console.WriteLine(x);
if (x == 1) {
hello.Add(100);
}
}
I would personally try to avoid doing it in the first place though - it can get very hard to reason about whether you'll ever complete, or if you'll skip items (if you're removing instead of adding, or adding before your current position).
You can't. You should create a new list and store the values in there.
public static void RunSnippet()
{
List<int> hello = new List<int>();
List<int> additions = new List<int>();
hello.Add(1); hello.Add(2); hello.Add(3);
foreach (var x in hello)
{
Console.WriteLine(x);
if (x == 1) {
additions.Add(100);
}
}
hello.AddRange(additions);
}
Use a snapshot of it instead:
foreach (var x in hello.ToArray())
{
// whatever here
}
Problem solved! Well, in a way. Items added during iteration would not be included.
No, you can't iterate over a list and modify them in the same iteration. Use a new list instead.
I found that there is stack in C#. I guess I could use stack.
public static void RunSnippet()
{
Stack<int> hello = new Stack<int>();
hello.Push(1); hello.Push(2); hello.Push(3);
while (hello.Count > 0)
{
int x = hello.Pop();
Console.WriteLine(x);
if (x == 1) {
hello.Push(100);
}
}
}
Using foreach you can't! You could use a for-loop, but it's very very bad style to do things like this. Things like this make your code very error prone, unpredictable and hard to debug.
There are answers that claims what you want cannot be achieved with foreach. That claim is wrong, all you need to do is to write a custom class with a custom enumerator.
public class CustomList : IEnumerable<int>
{
readonly List<int> list = new List<int>{1,2,3,4};
private int now = 0;
public void Add(int n)
{
list.Add(n);
}
public IEnumerator<int> GetEnumerator()
{
while (now<list.Count)
{
yield return list[now];
now++;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Now the following piece of code will print 1,2,3,4 and 100 to the screen:
var list = new CustomList();
foreach (int n in list)
{
if(n==1)
list.Add(100);
Console.WriteLine(n);
}
But I write this only as a proof of concept. You don't want to do this. If you will only add new items on the back, use Queue as others has said. If you will always add new items on the front, use Stack. If you will need both, write a custom LinkedList class with Dequeue (=Pop), Enqueue and Push operations, use something like :
while(list.notEmpty())
var item = list.Dequeue();
//bla bla
and you are all set. (You could even write a custom Enumerator again, to use with foreach, but we are destructing the list as we go, so it is against the spirit of Enumerations, and why bother in any case)
I'm trying to sort a list of telegramms to a List of Slaves.
If the PrimeAddress and the SecondaryAddress match, the telegrams belongs to the Slave.
The devices are stored in a Datatable.
I want to check if the deivce already contains the telegramm.
My first attempt looks something like this:
public static DataTable mdlform_NewMBUStele(int LoggerID, List<MbusTelegram> mList, DataTable _deviceDataTable)
{
//TODO Das ist total dirty und gar nicht clean hier...
foreach (DataRow dRow in _deviceDataTable.Rows)
{
if (dRow.ItemArray[3] is Slave)
{
foreach (MbusTelegram mb in mList)
{
int primeID = (int)dRow.ItemArray[1];
if (primeID == LoggerID)
{
Slave slv = (Slave)dRow.ItemArray[3];
foreach (MbusTelegram mbus in mList)
{
if (slv.PrimeAddress == mbus.Header.PrimeAddress && slv.SecondaryAdd == mbus.FixedDataHeader.SecondaryAddress)
{
if (slv.ListOfTelegramms == null)
{
slv.ListOfTelegramms = new List<MbusTelegram>();
}
if (!slv.ListOfTelegramms.Contains(mbus))
{
slv.ListOfTelegramms.Add(mbus);
//TODO Check if the slave already contains the telegramm, if so don't add it..
}
}
}
}
}
}
}
return _deviceDataTable;
}
Structure of the datatable:
private void IniDataTable()
{
_deviceDataTable = new DataTable("Table");
_deviceDataTable.Columns.Add("ID", typeof(int));
_deviceDataTable.Columns.Add("IDParent", typeof(int));
_deviceDataTable.Columns.Add("Name", typeof(string));
_deviceDataTable.Columns.Add("Object", typeof(object));
_deviceDataTable.Rows.Add(new object[] { 0, DBNull.Value, "Addressen", null });
//GenerateDummyDataTable();
IniDeviceTreeView();
}
This code doesn't work very well and it doesn't check if the device already contains the telegramm. Any better ideas?
There's a few things you can do to optimize your code. Firstly take all the things that doesn't change out of the inner loop. E.g. every type you use ItemArray. They are not changing for each iteration of the inner loop but with each iteration of the outer loop secondly you seem to be iterating twice over the same collection never using the variable of the outer loop (mb) so you can eleminate that loop entirely. I've done both in the code below. I've also converted the inner most loop to LINQ but that's mostly because I find it easier to read. I'm not sure the Distinct solves the TODO. I'm not sure I get the TODO at all. It might and it might not solve it you'll need to verify that
public static DataTable mdlform_NewMBUStele(int LoggerID, List<MbusTelegram> mList, DataTable _deviceDataTable){
//TODO Das ist total dirty und gar nicht clean hier...
foreach (DataRow dRow in _deviceDataTable.Rows.Cast<DataRow>().Where(d=>d.ItemArray[3] is Slave)){
var primeID = (int) dRow.ItemArray[1];
var slv = (Slave) dRow.ItemArray[3];
if (slv.ListOfTelegramms == null){
slv.ListOfTelegramms = new List<MbusTelegram>();
}
var list = slv.ListOfTelegramms;
if (primeID == LoggerID){
var items = from m in mList
where
slv.PrimeAddress == m.Header.PrimeAddress &&
slv.SecondaryAdd == m.FixedDataHeader.SecondaryAddress && !list.Contains(m, MbusTelegramEqualityComparer.Default)
select m;
list.AddRange(items.Distinct(MbusTelegramEqualityComparer.Default));
}
}
return _deviceDataTable;
}
if you also want to LINQify it for readability you could do as below (this might perform slightly worse):
public static DataTable mdlform_NewMBUStele2(int LoggerID, List<MbusTelegram> mList, DataTable _deviceDataTable){
var pairs = from dRow in _deviceDataTable.Rows.Cast<DataRow>()
where dRow.ItemArray[3] is Slave
let primeID = (int) dRow.ItemArray[1]
let slv = (Slave) dRow.ItemArray[3]
let list = slv.ListOfTelegramms
where primeID == LoggerID
select
new{
list,
items = (from m in mList
where
slv.PrimeAddress == m.Header.PrimeAddress &&
slv.SecondaryAdd == m.FixedDataHeader.SecondaryAddress &&
!list.Contains(m, MbusTelegramEqualityComparer.Default)
select m).Distinct(MbusTelegramEqualityComparer.Default)
};
foreach (var pair in pairs){
pair.list.AddRange(pair.items);
}
return _deviceDataTable;
}
the latter exampl requires that ListOfTelegrams is initialized to an empty list instead of null
EDIT
to have value comparison use a IEqualityComparer. The below uses the timestamps only for equality. The code is updated with the usage. If the ListOfTelegrams can contain duplicates you'll need to handle this in the call to distinct as well otherwise you can leave the call to Distinct out.
public class MbusTelegramEqualityComparer : IEqualityComparer<MbusTelegram>{
public static readonly MbusTelegramEqualityComparer Default = new MbusTelegramEqualityComparer();
public bool Equals(MbusTelegram x, MbusTelegram y){
return x.TimeStamp == y.TimeStamp;
}
public int GetHashCode(MbusTelegram obj){
return obj.TimeStamp.GetHashCode();
}
}
I am trying to do this:
foreach (Settings sets in MySets)
{
if (sets.pName == item.SubItems[2].Text)
{
var ss = new SettingsForm(sets);
if (ss.ShowDialog() == DialogResult.OK)
{
if (ss.ResultSave)
{
sets = ss.getSettings();
}
}
return;
}
}
But since the sets spawned variable is readonly, I cant override it.
I would also like to do something like this
foreach (Settings sets in MySets)
{
if(sets.pName == someName)
sets.RemoveFromList();
}
How can I accomplish this? Lists have a very nice Add() method, but they forgot the rest :(
You can use:
MySets.RemoveAll(sets => sets.pName == someName);
to remove all the items that satisfy a specific condition.
If you want to grab all the items satisfying a condition without touching the original list, you can try:
List<Settings> selectedItems = MySets.FindAll(sets => sets.pName == someName);
foreach loops don't work here as trying to change the underlying list will cause an exception in the next iteration of the loop. Of course, you can use a for loop and manually index the list. However, you should be very careful not to miss any items in the process of removing an item from the list (since the index of all the following items will get decremented if an element is removed):
for (int i = 0; i < MySets.Count; ++i) {
var sets = MySets[i]; // simulate `foreach` current variable
// The rest of the code will be pretty much unchanged.
// Now, you can set `MySets[i]` to a new object if you wish so:
// MySets[i] = new Settings();
//
// If you need to remove the item from a list and need to continue processing
// the next item: (decrementing the index var is important here)
// MySets.RemoveAt(i--);
// continue;
if (sets.pName == item.SubItems[2].Text)
{
var ss = new SettingsForm(sets);
if (ss.ShowDialog() == DialogResult.OK)
{
if (ss.ResultSave)
{
// Assigning to `sets` is not useful. Directly modify the list:
MySets[i] = ss.getSettings();
}
}
return;
}
}
You can't do it in a 'regular' for loop?