Assuming I have some objects like this:
Class NetworkSwitch
{
private String _name;
String name { get {return _name;} set {_name=value;}}
Dictionary<int, VLAN> VLANDict = new Dictionary<int, NetworkSwitch>();
public List<CiscoSwitch> GetAllNeigbors()
{
List<CiscoSwitch> templist = new List<CiscoSwitch>();
foreach (KeyValuePair<int, CiscoVSAN> vlanpair in this.VLANDict)
{
templist.AddRange((vlanpair.Value.NeighborsList.Except(templist, new SwitchByNameComparer())).ToList());
}
return templist;
}
Class VLAN
{
private Int _VLANNum;
Int VLANNum {get {return _VLANNum ;} set {_VLANNum =value;}}
//a neighbor is another switch this switch is connected to in this VLAN
// the neighbor may not have all same VLANs
List<NetworkSwitch> Neighbors = new List<NetworkSwitch>();
}
the above is designed that way because two switches that are physically connected may not have all the same VLANs assigned. what I am attempting to do is step through the Neighbors list in each VLAN on a given switch and update the reference to another switch if the name matches one in an input list. Here is what I tried and it won't compile. I am wondering if LINQ can do it in place somehow, or if there is a better approach.
// intersect is the input list of NetworkSwitch objects
//MyNetworkSwitch is a previously created switch
foreach (NetworkSwitch ns in intersect)
{
foreach (KeyValuePair<int, VLAN> vlanpair in MyNetworSwitch.VLANDict)
{
foreach (CiscoSwitch neighbor in vlanpair.Value.Neighbors)
{ // this is the line that fails - I can't update neighbor as it is part of the foreach
if (ns.name == neighbor.name) { neighbor = ns; }
}
}
}
Another question - I added the method that gets all the neighbors for a NetworkSwitch object. Assuming I were to get that list, then update it with references to a different instance of the switch with the same name, would that update the reference in the VLAN for the NetworkSwitch object?
Something like this should work:
foreach (NetworkSwitch ns in intersect)
{
foreach (KeyValuePair<int, VLAN> vlanpair in ns.VLANDict)
{
if(vlanpair.Value.Neighbors.RemoveAll(n => n.name == ns.name) > 0)
vlanpair.Value.Neighbors.Add(ns);
}
}
Due to how IEnumerable works, changing the contents of an Enumerable while iterating over it is not a supported action.
You will either have to return a new list with the changed values and then update the original reference, or use a plain ol' for (...; ...; ...) loop.
Related
I feel stupid for asking this but I am struggling to quite understand foreach. Say for example that I am making a book repository app, with a Book class as well as an Inventory class. The Inventory class has a removeBook method that removes a book from the inventory. The parameter for the method would be an int bookID. I am thinking I should use foreach to accomplish this. I understand the most basic use of foreach but I can not figure out how to use it to basically select a specific bookID that is a parameter in the method. Could someone help point me in the right direction?
Here's a code snippet, I know the method is wrong:
List<Book> Books = new List<Book>
{
new Book{ bookID = 5, Name = "Moby Dick", Price = 20.00 },
new Book{ bookID = 2, Name = "50 Shades of Grey", Price = 0.99 }
};
public void removeBook(int bookID)
{
foreach (var bookID in Books)
{
Products.Remove(book);
}
}
Removing a thing from a collection isn't what foreach is for - it's for performing some operation on every value in the collection. If you want to remove a book with a specific id, you could use a regular for loop:
// in Inventory class having List<Book> Books,
// assuming Book has a public int Id property
public void RemoveBook(int bookId) {
for (int i = 0; i < this.Books.Count; i++) {
if (this.Books[i].Id == bookId) {
this.Books.RemoveAt(i);
return;
}
}
}
If there are any duplicates for some reason (there shouldn't be - IDs should be unique) and you wanted to remove all books with given ID, this code should do it:
public void RemoveBooks(int bookId) {
// iterating from the end of the array
// to prevent skipping over items
for (int i = this.Books.Count - 1; i >= 0; i--) {
if (this.Books[i].Id == bookId) {
this.Books.RemoveAt(i);
}
}
}
EDIT: fixed the code, thanks to Gerardo Grignoli
So if you want to remove a book from a list you would not necessarily use a foreach loop. Simplest way would be to use the RemoveAll function on List.
public void RemoveBook(int bookId) =>
Books.RemoveAll(book => book.Id == bookId);
If you have
List<Thing> things = ....
Then in a foreach like this
foreach (Thing theThing in things)
{
// do something with "theThing"
}
the foreach loops through all items in the things list and executes that block of code (between the { }) for each consecutive value, which is stored in the theThing variable (and is of type Thing).
You can replace Thing theThing with var theThing, for the exact same result.
foreach wont work if you are iterating over the same list so use for loop or linq instead
MSDN
The foreach statement is used to iterate through the collection to get the information that you want, but can not be used to add or remove items from the source collection to avoid unpredictable side effects. If you need to add or remove items from the source collection, use a for loop.
private void RemoveBookFromInventory(int bookID)
{
foreach(Books book in listOfBooks)
{
if(book.bookID == bookID)
{
listOfBooks.Remove(book); //wont work
}
}
for(int i=0;i<listOfBooks.Count();i++)
{
if (listOfBooks[i].bookID == bookID)
listOfBooks.Remove(listOfBooks[i]);
}
}
As others have implied, you can't really change the contents of a list while iterating through it with a for loop, as that would change the data structure you are working on.
There are several other ways to solve this though. I personally like solutions in this style, where you fetch the index of the book first, and then remove it in a separate step afterwards:
var bookID = 2;
int? index = Books
// Generate a list of book- and position- pairs:
.Select((book, pos) => new {book, pos})
// First matching pair (or null):
.FirstOrDefault(set => set.book.bookID == bookID)?
// If NULL was not returned, get the index property:
.pos;
// Removal is only attempted if a matching book was found:
if(index.HasValue){
Books.RemoveAt(index.Value);
}
I'm using foreach to transfer data from list to another but when adding value updated automatically to last value added. For example:
list1 = [1,2,3]
list2 = new List<Model>()
foreach(var item in list1) {
list2.Add(item)
}
the result in list2 is [ 3, 3, 3]
Actually example is below :
var _sizes = new List<ProductsSize>();
var _size = new ProductsSize();
if (model.Dynamic_ProductsSize.Count > 0)
{
foreach (var item in model.Dynamic_ProductsSize)
{
_size.SizeId = item;
_sizes.Add(_size);
}
}
model.ProductsSize = _sizes.ToList();
I need to know why it only takes the last item and what is the solution for this case
You only have one ProductsSize object:
var _size = new ProductsSize();
And you keep modifying that same object. All references to that object, including any list elements it's been added to, get updated when you modify that one object.
Instead, create your new object in the loop:
foreach (var item in model.Dynamic_ProductsSize)
{
var _size = new ProductsSize();
_size.SizeId = item;
_sizes.Add(_size);
}
That way each element in the list is a new object instead of the same object added multiple times.
Side note, you have a few things in the code which aren't necessary. Checking the length before the loop, for example, as well as converting a list to a list at the end.
In fact, I imagine all of the code shown can be shortened to simply this:
model.ProductsSize = model.Dynamic_ProductsSize.Select(p => new ProductsSize { SizeId = p }).ToList();
In which case you're also just converting one model property to another model property. Why not put this logic in the model itself and skip the whole thing?
public IEnumerable<ProductsSize> ProductsSize
{
get { return this.Dynamic_ProductsSize.Select(p => new ProductsSize { SizeId = p });
}
Unless there's a particular reason you want the same data twice in two different properties that isn't clear from this code, having one set of data and just different views/calculations/etc. of that data is often preferred.
Create a new object before adding it to the list. You can use the object initializer syntax to keep it concise:
if (model.Dynamic_ProductsSize.Count > 0)
{
foreach (var item in model.Dynamic_ProductsSize)
{
_sizes.Add(new ProductsSize(){SizeId = item});
}
}
I have multiple lists of polygons that each represent an physical object. For example:
List<CurveLoop> A could represent a rectangle with a hole in it. One curve within this list would be the outline of the rectangle, and another curve would be the hole.
I want a method that will return a list of lists, where each list contains all the objects that intersect.
I already have a method that will return whether two of the objects intersect:
bool _CurveLoopsIntersect(List<CurveLoop> curveLoopsA, List<CurveLoop> curveLoopsB) {...}
will return true if any any two curves within the two lists touch.
Below is the code I have so far, but it just gives me a single pass. I think I need multiple passes, so that if object A and B intersect, and B and C intersect, then they would form set { A, B, C }. I need an arbitrary number of passes though, and sometimes the objects won't intersect at all, or be part of different sets, such as {A, B, C} and {D, E} and {F}.
public List<CurveLoop> _MergeCurveLoops(List<List<CurveLoop>> elementCurveLoops, View view)
{
// ...
// Preprocessing
var listOfLists = new List<List<CurveLoop>>();
foreach (var elementCurveLoop in elementCurveLoops)
{
var newList = elementCurveLoops.FindAll(x => _CurveLoopsIntersect(x, elementCurveLoop));
listOfLists.Add(newList);
}
}
private bool _CurveLoopsIntersect(List<CurveLoop> curveLoopsA, List<CurveLoop> curveLoopsB)
{
foreach (var curveLoopA in curveLoopsA)
{
foreach (var curveA in curveLoopA)
{
foreach (var curveLoopB in curveLoopsB)
{
foreach (var curveB in curveLoopB)
{
var result = curveA.Intersect(curveB);
if (result == SetComparisonResult.Overlap ||
result == SetComparisonResult.Subset ||
result == SetComparisonResult.Superset ||
result == SetComparisonResult.Equal)
{
return true;
}
}
}
}
}
return false;
}
This can be implemented using some code like this psuedu
set = a,b,c, ...
While(set not empty) {
Create newSet
Add set.first to new list
Remove set.first from set // this line isnt necessary if a curve doesnt intersect with self
For (i = 0 , i < newset.length , i++)
{
newSet.add(set.FindAll(x => _CurveLoopsIntersect(x, newSet[i]));
set.removeRange(newSet); // this line may have error that the first element doesnt exist in set
}
Add newSet to set of sets
}
Thanks, you put me in the right direction. You were right, using a Set was the right approach. I used a set in combination with a recursive function (similar to your while loop).
The code I wrote is below:
static List<Polygon> _RecursiveMergePolygons(List<Polygon> polygons, View view)
{
HashSet<Polygon> initialSet = new HashSet<Polygon>(polygons);
HashSet<Polygon> finalSet = new HashSet<Polygon>(polygons);
foreach (var polygon in initialSet)
{
// Should always return at least 1 instance
var polys = polygons.FindAll(x => _PolygonsIntersect(x, polygon));
// if it's greater than 1, then merge them and restart the recursion, otherwise continue
if (polys.Count > 1)
{
foreach (var poly in polys)
{
finalSet.Remove(poly);
}
var mergedPolygon = new Polygon(polys, view);
finalSet.Add(mergedPolygon);
break;
}
}
if (finalSet.Count == initialSet.Count)
{
return finalSet.ToList();
}
return _RecursiveMergePolygons(finalSet.ToList(), view);
}
Let's have a simple class with 2 fields
public class Sample
{
public int IdOfSample;
public string SampleName;
}
And another using this one
public class ListOfSamples
{
public int IdOfList;
public List<Sample> SampleList;
}
And finally, since we will use a couple of different ListOfSamples, make a list of them:
public static List<ListOfSamples> FinalList = new List<ListOfSamples>();
Now the problem:
I create a new Sample (let's call it NewItem), with some name and Id. I want to check if there's a ListOfSamples in my FinalList that as the same Id as the NewItem I have. Otherwise create new ListOfSamples in the FinalList with the IdOfList = NewItem.IdOfSample.
I think I got the first part which checks if you should add a new list (ie. a ListOfSamples with specified IdOfList does not exist:
Sample NewItem = new Sample()
{
IdOfSample = 12345,
SampleName = "Some name"
};
int index = FinalList.FindIndex(f => f.IdOfList == NewItem.IdOfSample);
if (!FinalList.Any() || index == -1)
{
ListOfSamples NewList = new ListOfSamples()
{
IdOfList = NewItem.IdOfSample,
SampleList = new List<Sample>()
};
NewList.SampleList.Add(NewItem);
FinalList.Add(NewList);
}
Now, I'm trying to construct a statement, that, if the list with specified Id already exists in the FinalList, just add the new item to it, but so far I think my limited experience with LINQ is showing, nothing I try seems to work.
So:
If there exists a ListOfSamples with IdOfList == NewItem.IdOfSample in FinalList, then add NewItem to that ListOfSamples.
How about
if (!FinalList.Any() || index == -1)
...
else
{
FinalList[index].SampleList.Add(NewItem);
}
If you just wanted to check whether the list item existed, a suitable LINQ statement could be:
if (FinalList.Any(l => l.IdOfList == NewItem.IdOfSample))
{
// ...
}
Given you want to work on the item then you could attempt to retrieve it as follows:
var existingList = FinalList.SingleOrDefault(l => l.IdOfList == NewItem.IdOfSample);
if (existingList != null)
{
existingList.Add( ... );
}
Though perhaps it's worth thinking about using a HashSet of lists if you want to guarantee uniqueness...
if i understand it right ...
// search for the list with the given Id
var listOfSamples = finalList.Where(fl => fl.IdOfList == newItem.IdOfSample).FirstOrDefault();
if (listOfSamples == null)
{
// not found
// add new List with the new item in final list
finalList.Add(new ListOfSamples {IdOfList = newItem.IdOfSample, SampleList = new List<Sample>{newItem}} );
}
else
{
// found
// add the new item into the found list
listOfSamples.SampleList.Add(newItem);
}
If you replace ListOfSamples with a Dictionary<int, List<Sample>> then you will gain the ability to do a lookup in O(1) time and guarantee that the ids at the top level are unique. and then you can just add stuff like this.
Dictionary<int, List<Sample>> FinalList = new Dictionary<int, List<Sample>>();
Sample NewItem = new Sample()
{
IdOfSample = 12345,
SampleName = "Some name"
};
List<Sample> list;
if (!FinalList.TryGetValue(NewItem.IdOfSample, out list))
{
list = new List<Sample>();
FinalList.Add(NewItem.IdOfSample, list);
}
list.Add(NewItem);
TryGetValue will see if the dictionary has an entry for the key you pass it and returns true if it does and false if it does not. If it does have an entry for the key it also assigns the value of the entry (in this case your list of samples) to the out parameter. So, we check if it returns false and in that case we create a new list and add it to the dictionary. Then we add the sample to the list that we either got from the dictionary, or just created and put in the dictionary.
Collection was modified; enumeration operation may not execute.
I get that error when I run my program.
I have a dictionary and when I modify it I can't enumerate from it.
public void Render() {
foreach (var pair in players ) {
Main.SpriteBatch.DrawString(Main.BigFont, pair.Key, pair.Value, Color.White);
}
}
I tried modifying it at the value or removing it completely, then it must re-add it.
byte[] data = client.EndReceive(ar, ref ip);
string str_data = Encoding.ASCII.GetString(data);
string[] coords = str_data.Split(";".ToCharArray());
if (players.ContainsKey(coords[0])) {
players.Remove(coords[0]);
}
players.Add(coords[0], new Vector2(Int32.Parse(coords[1]), Int32.Parse(coords[2])));
You can also try to use an ordinary for loop instead of a foreach.
That way you can circumvent the warning, since you're not using the enumeration option.
For a dictionary you would do:
List<string/*or other datatype*/> keys = players.Keys.ToList();
for (int i = 0; i < keys.Count; i++)
{
Main.SpriteBatch.DrawString(Main.BigFont, keys[i], players[keys[i]], Color.White);
}
Does this suit your needs?
var ToBeDeleted = new List<player>(); //Or whatever type was the list players holding
if (players.ContainsKey(coords[0])) {
ToBeDeleted.Add(coords[0]);
}
foreach(var item in ToBeDeleted)
{
players.Remove(item);
}
You cannot modify (add, update or remove) any collection while you are iterating over it. It is restricted in c#.
You need to modify your code so that you keep any updation on the list seperate. You can keep a seperate record for any changes that you have made during the loop and merge after the loop.e.g
var itemsToDelete = new Dictionary<int,Player>();
then change your this line
if (players.ContainsKey(coords[0])) {
players.Remove(coords[0]);
}
to
if (players.ContainsKey(coords[0])) {
itemsToDelete.Add(coords[0]);
}
then after your loop
foreach (var key in itemsToDelete)
{
players.Remove(key);
}