Yesterday I wrote a piece of code to remove all the controls in a form that fulfills certain criteria. Writing it naively, this is what I come up with.
for (int i = 0; i < this.Controls.Count; ++i)
{
if (this.Controls[i].Name.Length == 2)
{
this.Controls.Remove(this.Controls[i);
}
}
But it so happens that the code is wrong. I then change it to:
foreach (Control ctr in this.pbBoardImage.Controls)
{
if (ctr.Length == 2)
{
this.Controls.Remove(ctr);
}
}
But it still wasn't correct.
I know that the correct way would be:
for (int i = this.Controls.Count - 1; i >= 0; i--)
{
if (this.Controls[i].Name.Length == 2)
{
this.Controls.Remove(this.Controls[i]);
}
}
However it still doesn't feel elegant. I couldn't use List.RemoveAll, since this.Controls wasn't a List. So can I ask for a more elegant way, preferably without using a loop?
Not sure why you didn't like this answer... I've highlighted the important RemoveAt; however, as an alternative in .NET 3.5/C# 3.0: LINQ:
var qry = from Control control in Controls
where control.Name.Length == 2
select control;
foreach(var control in qry.ToList()) {
Controls.Remove(control);
}
(original)
You can't Remove within foreach - it breaks the iterator. A common approach here is to iterate backwards:
for (int i = this.Controls.Count - 1; i >= 0; i--) {
if (this.Controls[i].Name.Length == 2) {
this.Controls.RemoveAt(i); // <=========== *** RemoveAt
}
}
This avoids the "off by one" issues, etc.
Related
I need to use linq as many times as possible and I have no idea how to use linq in this type of method.
I've tried some code from certain webs however none of them worked
List<MemorableD> memorables = new List<MemorableD>();
List<StateMD> states = new List<StateMD>();
void Find(List<MemorableD> selected)
{
for (int i = 0; i < states.Count; i++)
{
for (int j = 0; j < memorables.Count; j++)
{
if (states[i].Month == memorables[j].Month && states[i].Day == memorables[j].Day)
{
MemorableD select = new MemorableD(memorables[j].Year, memorables[j].Month, memorables[j].Day, memorables[j].Event, states[i].Event);
selected.Add(select);
}
}
}
}
I need to write this add method with LINQ
Try to break down your problem. If you were to analyse your loops, you are iterating over the States and Memorables, and creating instances of MemorableD where State and Memorable have the same Month and Day and latter adding them to the List.
Translating it to Linq,
from StateMD state in states
from MemorableD memorable in memorables
where state.Month == memorable.Month && state.Day == memorable.Day
let selectValue = new MemorableD(memorable.Year, memorable.Month, memorable.Day, memorable.Event, state.Event)
select selectValue
The second part of the problem is to add it to the List called selected. You can add an IEnumerable to selected using the AddRange method.
So, combining the Linq statement with AddRange method,
selected.AddRange(from StateMD state in states
from MemorableD memorable in memorables
where state.Month == memorable.Month && state.Day == memorable.Day
let selectValue = new MemorableD(memorable.Year, memorable.Month, memorable.Day, memorable.Event, state.Event)
select selectValue);
Hey so I'm trying to execute this bit of code, however it is going out of bounds I would assume due to it trying to execute the first loop at the index that was removed. Does anyone know a way I can execute this code without it going out of bounds?
for (int i = myList1.Count - 1; i >= 0; i--)
{
for (int j = 0; j < myList2.Count - 1; j++)
{
if (myList2[j] != myList1[i])
{
myList1.RemoveAt(i);
}
}
}
Obligitory Linq Answer:
myList1 = myList1.Where(i => !myList2.Contains(i)).ToList();
Basically, instead of looping through them both on your own, you use Linq to do it for you. You set myList1 to be the items matching the where clause of [myList2 does not contain item X].
I'm trying to modify a list inside a for value
for (int i = 0; i < theList.Count; i++) {
if(someCircunstances)
theList.remove(component);
else
theList.add(component);
}
I get an ArgumentOutOfRangeException with this method.
Is there any method to accomplish this?
It can be solved by iterating backwards and using indexes instead of items:
for (int i = list.Count - 1; i > 0; i--)
{
if(condition)
list.RemoveAt(i);
else
list.Add(component);
}
Some explanation: when you iterating over collection you shouldn't change items in the scope. Iterators will detect that and throw (and in case of foreach you must use copy of list). But in case of using indexes (RemoveAt() method) and when iterating backward you are safe as for next iteration the scope doesn't include deleted items. Add() is adding to the end, therefore new item is never in scope.
I'll add few more solutions, which one is better decide yourself:
Classical foreach with copy first:
foreach(var item in list.ToArray()) // imho `ToArray` is better than `ToList`
if(condition)
list.Remove(item);
else
list.Add(component);
New list as result:
var result = new List<...>();
foreach(var item in list)
result.Add(condition ? component : item); // not sure here, but should give you idea
list = result;
This is also a bad practice to mutate the list while iterating over it.
This is an alternative:
theList.RemoveAll(someCircunstances);
you are getting an out of range exception because indexes start on 0.
as stated above, one solution is to remove 1 from theList.count, and another solution is to initiate i at 1 instead of 0.
think of this: if your list has 1 element in it, the index of that element is 0, if you have 100 elements, the index of your hundreth element is 99.
you are thinking of the list like: [1][2][3], while it's actually [0][1][2]
The problem here is that you are deleting values out of the list and then you iterate throught it again with an index which is already removed -> ArgumentOutOfRangeException
So to solve this i suggest you to split it up to two for loops:
for (int i = theList.Count - 1; i >= 0; i--) {
if(someCircunstances)
theList.remove(component);
}
for (int i = 0; i < theList.Count; i++) {
if(someCircunstances)
theList.add(component);
}
I am agree with Tamas, that don't mutate the list while iterating , there is another way to achieve your point
List<someType> ToRemove=new List<someType>() ; //some type is same type as theList is
List<someType> ToAdd=new List<someType>();
for (int i = 0; i < theList.Count; i++) {
if(someCircunstances)
ToRemove.add(component);
else
ToAdd.add(component);
}
theList=((theList.Except(ToRemove)).Concat(ToAdd)).ToList();
Based on the comments, you need to be able to apply the same logic for newly created items.
You need to do something like this:
public void DoIt(List<MyObject> theList)
{
List<MyObject> items_to_remove = new List<MyObject>();
List<MyObject> items_to_add = new List<MyObject>();
for (int i = 0; i < theList.Count; i++)
{
if (someCircunstances)
items_to_remove.Add(....); //Remove some existing item
else
items_to_add.Add(....); //Add a new item
}
if(items_to_remove.Count > 0)
items_to_remove.ForEach(x => theList.Remove(x));
if (items_to_add.Count > 0)
{
DoIt(items_to_add); //Recursively process new objects
theList.AddRange(items_to_add);
}
}
The idea is that you insert the items to add and the items to remove in their own lists.
Then after the iteration, you remove the items that need to be removed.
After that you need to add the items to add. However, before doing that you need to run the same logic on them, and that is the explanation for the recursive call.
Please note that I am using MyObject because I don't know the type of your list. Use whatever type that you are working with.
If you can use the current index of the loop to remove the item from the lst, you can do this easily like so:
using System;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
var numbers = Enumerable.Range(1, 20).ToList();
var rng = new Random();
for (int i = 0; i < numbers.Count; ++i)
{
if (rng.NextDouble() >= 0.5) // If "someCircumstances"
{
numbers.Add(numbers[i]*2);
}
else
{
// Assume here you have some way to determine the
// index of the item to remove.
// For demo purposes, I'll just calculate a random index.
int index = rng.Next(0, numbers.Count);
if (index >= i)
--i;
numbers.RemoveAt(index);
}
}
Console.WriteLine(string.Join("\n", numbers));
}
}
}
This will also loop over all the numbers added to the end of the list. The value of numbers.Count is recomputed at each iteration, so when it changes, the loop will be extended appropriately.
(Offtopic) BONUS QUESTION: In the above code, what will be the average size of the list when the loop exits? And what would be the maximum size?
I am new to C# programming. I have number of text boxes on a form and instead of writing the same code for each text box, is it possible to use loop for writing same code for each text box? In the code below can we use the variable i for the same?
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 3; i++)
{
MessageBox.Show(amountTextBox.Text);
MessageBox.Show(amountTextBox1.Text);
MessageBox.Show(amountTextBox2.Text);
MessageBox.Show(amountTextBox3.Text);
}
}
Thanks in advance.
Instead of trying to generate code (which is a lot harder than it sounds), whenever you have variables named something1, something2, … somethingN, you should consider using an array, or some other collection.
Create an array of text boxes, like this:
var amountTextBoxes = new[] { amountTextBox, amountTextBox1, amountTextBox2, amountTextBox3 };
And then loop through them like this:
for (int i = 0; i < amountTextBoxes.Length; i++)
{
MessageBox.Show(amountTextBoxes[i].Text);
}
Or like this:
foreach (var textBox in amountTextBoxes)
{
MessageBox.Show(textBox.Text);
}
Another option that would probably work in this specific case, (though this solution is not as general as the previous one) involves directly searching for the controls based on the name. If this is a Windows Forms application, you could use Find (assuming all controls have the same name pattern):
for (int i = 0; i < 3; i++)
{
var controlName = "amountTextBoxes" + i;
vat textBox = (TextBox)this.Controls.Find(controlName, true)[0];
MessageBox.Show(textBox.Text);
}
Or if this is WPF, you could use FindName:
for (int i = 0; i < 3; i++)
{
var controlName = "amountTextBoxes" + i;
vat textBox = (TextBox)this.FindName(controlName);
MessageBox.Show(textBox.Text);
}
You can try something like this
foreach (Control x in this.Controls)
{
if (x is TextBox)
{
MessageBox.Show(((TextBox)x).Text);
}
}
Better version with OfType extension method:
foreach (TextBox x in this.Controls.OfType<TextBox>().OrderBy(x => x.Name))
{
MessageBox.Show(x.Text);
}
Here is a linq version.
this.Controls
.OfType<TextBox>()
.ToList()
.ForEach(control =>
MessageBox.Show(control.Text));
Instead of using for loop you can do like this also..
*note * - This is for all the textbox controls in the form.. for specific textboxes you may use a for loop which loop through an array or collection of text boxes you need .
foreach(Control control in this.Controls)
{
if(control.GetType() == typeof(TextBox))
{
MessageBox.Show(control.Text);
}
}
As Selman22 mentioned you can solved it using this.Controls.OfType(). You can further refine your query as mentioned below
foreach (TextBox textBox in this.Controls.OfType<TextBox>().Where(x => x.Name.Contains("amountTextBox")).OrderBy(x => x.Name).ToList())
{
MessageBox.Show(textBox.Text);
}
A simple and maintainable way would be to yield return every textbox you're interested in, in the order that you're interested in, and then iterate over that.
private void button1_Click(object sender, EventArgs e)
{
foreach (var textBox in GetAmountTextBoxes())
MessageBox.Show(textBox.Text);
}
private IEnumerable<TextBox> GetAmountTextBoxes()
{
yield return amountTextBox;
yield return amountTextBox1;
yield return amountTextBox2;
yield return amountTextBox3;
}
If you have many textboxes and maintaining this list is cumbersome, you could generalise it to something like:
return Controls.OfType<TextBox>();
But take care to filter it, if necessary, to only the textbox controls you need with Where.
And if order is important, you'll need to devise some function to sort the textboxes (in conjunction with OrderBy). This is necessary because the default sorting algorithm will sort TextBox10 before TextBox1.
Your Code Rewritten:
for (int i = 0; i < 3; i++)
{
MessageBox.Show(amountTextBox[i].Text);
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Foreach loop, determine which is the last iteration of the loop
foreach (DataRowView row in orderedTable.DefaultView)
{
if(lasttime) do-something;
}
orderedtable is a datatable
does anyone know how to find out whether we are on the last foreach iteration? please keep in mind that i do have duplicates in orderedtable
The correct method that works in all cases is to use the IEnumerator<T> directly:
using (var enumerator = orderedTable.DefaultView.GetEnumerator())
{
if (enumerator.MoveNext())
{
bool isLast;
do
{
var current = enumerator.Current;
isLast = !enumerator.MoveNext();
//Do stuff here
} while (!isLast);
}
}
This method works even if your collection doesn't have a Count property, and even if it does, this method will be more efficient if the Count property is slow.
The foreach construct does not know such a thing, since it applies equally to unbounded lists. It just has no way of knowing what is a last item.
You can iterate the manual way as well, though:
for (int i = 0; i < orderedTable.DefaultView.Count; i++) {
DataRowView row = orderedTable.DefaultView[i];
if (i == orderedTable.DefaulView.Count - 1) {
// dosomething
}
}
An alternative approach which I don't think anyone posted. This works well if you don't know the count ahead of time.
DataRowView lastRow;
foreach (DataRowView row in orderedTable.DefaultView)
{
// Do something...
lastRow = row;
}
if (lastRow != null)
{
// Do something with last row
}
You will have to use a regular for loop if you want to have different behavior on the last item.
for (int i = 0; i < orderedTable.DefaultView.Count; i++)
{
//do stuff
if (i == orderedTable.DefaultView.Count - 1)
{
//do additional special stuff
}
}
It's worth noting that "the other Skeet" has an implementation for a "smart enumerable" which supports a Last property. See the article here: http://msmvps.com/blogs/jon_skeet/archive/2007/07/27/smart-enumerations.aspx
With this you could write something like this (I might get the details wrong, haven't tried it out myself):
foreach (SmartEnumerable<DataRowView> item in new SmartEnumerable<DataRowView>(orderedTable.DefaultView))
{
DataRowView row = item.Value;
if(item.IsLast)
{
///do special stuff
}
}
Instead of using foreach get the IEnumerator. If MoveNext returns null the previous was the last one.
for would work too of course.
You could possibly do something like the following:
foreach (DataRowView row in orderedTable.DefaultView)
{
if(row == orderedTable.DefaultView.Last()) do-something;
}
But its pretty inefficient.
If you're concerned in keeping track of your iteration, why not use a for instead or a foreach? Then you can simply do the following:
for(int i = 0; i<orderedTable.DefaultView.Count-1;i++)
{
if(i==orderedTable.DefaultView.Count-1)
{
// Last Row
}
}
The code you have posted is identical to:
if (orderedTable.DefaultView.Rows.Count > 0)
do-something;
If that does not do what you want, you will have to explain what lastTime does.
If I understand your question, does this help?
int lastrow = 0;
foreach (DataRow row in table.Rows) // Loop over the rows.
{
lastrow++;
// Do Something
if (lastrow == (table.Rows.Count - 1))
{
// Do something else
}
}