This:
int j = 1;
int j = 2;
Console.WriteLine(j.ToString());
.. produces a compile error:
A local variable named 'j' is already defined in this scope
Yet this works fine:
for (int i = 0; i < 10; i++)
{
int j = i;
Console.WriteLine(j.ToString());
}
Why?
How does the loop simultaneous retain values from each iteration whilst being able to redeclare a variable with the same name in the same scope?
The variable j exists only per iteration, i.e. in each iteration a variable j is declared, assigned, used and then discarded and the next iteration begins and the process repeats and so forth. hence you don't get the same compilation error as the first example snippet.
When you write int j=1 and int j=2, you are trying to declare the variable twice (you can only declare it once).
You could, however, overwrite the value of j:
int j = 1; // now j has a value of 1
j = 2; // now j has a value of 2
This is what the for loop is doing - each iteration of the for loop, the value is updated. A new instance of j is not created during each iteration.
In the first example you are defining two variables with the same name, and they exist at the same time.
In the loop, every variable is created in the loop context. After each iteration the variable is destroyed, allowing you to create a new one with the same name(on the next iteration). In other words, on the loop they don't exist at the same time.
Related
I am trying to update an array and add it to a list if a certain condition is true. As you can see in my code the array "rows" is updated every time inside the if condition, and the it is added to "checkList".
The problem is that when I iterate through the list to check the values, it seems that only the last value of rows has been added in every entry in the list.
Here is some code to explain
int[] rows = new int[2];
List<int[]> checkList = new List<int[]>();
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (true)
{
rows[0] = i;
rows[1] = j;
checkList.Add(rows);
}
}
}
foreach (var row in checkList)
{
Console.WriteLine(row[0] + " " + row[1]);
}
Output:
I hope someone can explain this. Thanks
Most object types in .NET (including arrays) are passed by reference, so checkList.Add(rows); adds a reference to the same array to the list, multiple times.
Instead, you'll want to create a new array instance every time:
List<int[]> checkList = new List<int[]>();
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (true)
{
checkList.Add(new int[]{ i, j });
}
}
}
I believe the issue here is that when you are
checkList.Add(rows);
You are adding a reference to the rows array every time to the list, not a separate copy of it. This leads to your current behaviour.
A solution would be to instantiate the array inside the loop, so a new array is created every iteration.
List<int[]> checkList = new List<int[]>();
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (true)
{
int[] rows = new int[2];
rows[0] = i;
rows[1] = j;
checkList.Add(rows);
}
}
}
As a supplement to Matthias answer, one of the things that's perhaps not easy to appreciate about C# is that most variables you have and use are merely a reference to something else. When you assign some variable like this:
int[] rows = new int[2];
C# creates some space in memory to keep an array of 2 integers, it attaches a reference to it, and that thing becomes your variable that you use, named rows. If you then do:
int[] rows2 = rows;
It doesn't clone the memory space used and create a new array, it just creates another reference attached to the same data in memory. If the data were a dog, it now has 2 leads attached to its collar but there is still only one dog. You can pull on either lead to urge the dog to stop peeing on a car, but it's the same dog you're affecting.
Array/list slots are just like variables in this regard. To say you have:
List<int[]> checkList = new List<int[]>();
Means declare a list where each of its slots are a variable capable of referring to an int array. It's conceptually no different to saying:
int[] checkList0 = row;
int[] checkList1 = row;
int[] checkList2 = row;
int[] checkList3 = row;
It's just that those numbers are baked into the name, whereas a list permits you a way of varying the name programmatically (and having more than 4 slots):
checkList[0] = row;
checkList[1] = row;
checkList[2] = row;
checkList[3] = row;
checkList[0] is conceptually the entire variable name, just like checkList0 is a variable name, and remember that this is hence just another variable that is just a reference to that same array in memory.
By not making a new array each time, you attached every variable slot in the list to the same array in memory, and thus you ended up with something in memory that looks like:
The black is the list, the blue is the array. Every list slot is just a reference to the same array. You might have changed the numbers in the array 200 times, but at the end of the oepration, because there was only ever one array, you only see the final set of numbers you wrote into the array. You might have attached 20 leads to your dog and pulled each of them once, but it's still just the same dog that has 20 times been stopped from peeing on 20 cars.
Matthias answer works (and is how it should be done) because you concretely make a new array each time
Numbers in blue are fabricated and not intended to represent the answers you should see printed; the concept being explained is that of linking to new array objects in memory
You'd be forgiven for thinking that a clone would be made, bcause it is for int. int is a value type, whcih means the value is copied when it's used:
int x = 1;
int y = x;
y = y + 1;
y is now 2, but x is still 1. It'd be pretty hard work to write C# if it wasn't this way i.e. if every time you incremented some int variable, every other variable that had touched the variable that it came from was also affected.. So I think it's perhaps intrinsically reasonable to assume that whenever an assignment of anything is made, changes that affect the value of the assigned variable don't affect earlier iterations of it.. but that's not the case. There's this clear divide between value types (types whose data is copied/cloned when they're assigned) and reference types (types whose data is not copied/cloned). While int is a value type (cloned), an int[] is a reference type (not cloned)
..and that's something you'll really need to get down with and remember
Roll on the what's ref/out for? query :D
If I run this simple code in a console app:
For i As Integer = 1 To 10
Dim s As String = i.ToString()
Dim x As Decimal
If i = 1 Then
x = Decimal.Parse(s)
End If
Console.WriteLine(x.ToString())
Next
Console.ReadLine()
Unexpectedly, x retains its value of 1 and so 1 gets printed 10 times. I thought each iteration of the loop was its own code block, and that the state didn't carry over? Why does this happen? I would expect x to have the default value of System.Decimal.
Same thing happens in C#, except that the complier won't let you call ToString() on an uninitialized variable, but if you set a breakpoint in Visual Studio, you can see that x retains its value of 1.
for (int i = 1; i <= 10; i++)
{
string s = i.ToString();
Decimal x;
if(i == 1)
{
x = Decimal.Parse(s);
}
// Value of x remains 1
}
Console.ReadLine();
Regarding VB.NET take a look about scope here. In the "Block Scope" section there is a note which states the following:
Even if the scope of a variable is limited to a block, its lifetime is
still that of the entire procedure. If you enter the block more than
once during the procedure, each block variable retains its previous
value. To avoid unexpected results in such a case, it is wise to
initialize block variables at the beginning of the block.
So this behavior is by design and you should initialize the variable to whatever value your code needs.
I modified your code to show that the first time x is initialized to 0 but after that it retains the value of 1.
For i As Integer = 1 To 10
Dim s As String = i.ToString()
Dim x As Decimal
Console.WriteLine(x.ToString())
If i = 1 Then
x = Decimal.Parse(s)
End If
Console.WriteLine(x.ToString())
Next
Console.ReadLine()
thought each iteration of the loop was its own code block, and that the state didn't carry over
It does "carry over."
int i is scoped to the entire range of the loop, with values from 1 to 10.
The if statement only executes on the first iteration, and x is scoped as a local variable to the outer method (assuming the loop is entered), not only the loop body. That being said, after & outside the loop, x == 1 as well.
In fact, Decimal x; in the loop is redundant, and you have the same execution logic as
string s;
Decimal x;
for (int i = 1; i <= 10; i++)
{
s = i.ToString();
if(i == 1)
{
x = Decimal.Parse(s);
}
Console.WriteLine(x);
}
// x still defined here
To make some sense of what you want, it should be just like this:
For i As Integer = 1 To 10
If i = 1 Then
Console.WriteLine(Decimal.Parse(i.ToString()))
End If
Next
This will give you the same result you want but it is readable.
I am just new to c#, please help me understanding for loop in c#..
Issue: I am trying to use j further into my code but after exit out from for loop , it shows j is not exist in the current content.
Is this correct behavior in C# ? how do I get to use j outside the loop?
for (int i = 0; i < 4; i++)
{
int j;
j = 10;
//print j within the loop, which works fine
System.Windows.Forms.MessageBox.Show(j.ToString());
}
//print j outside the loop, which throws error
System.Windows.Forms.MessageBox.Show(j.ToString());
You need to get better acquainted with variable scopes in C# - there is a lot of documentation around if you google it.
As for your code, if you want to access the variable outside the for loop, you need to declare it outside the for loop, e.g.
int j = 0;
for (int i = 0; i < 4; i++)
{
j = 10;
System.Windows.Forms.MessageBox.Show(j.ToString());
}
System.Windows.Forms.MessageBox.Show(j.ToString());
In Your code You declare the variable j inside the loop that's why You cannot able to access the variable.
So declare the variable outside the for loop
just like below,
int j=0; // Declare here
for (int i = 0; i < 4; i++)
{
j = 10;
'print j within the loop, which works fine
System.Windows.Forms.MessageBox.Show(j.ToString());
}
'print j outside the loop, which throws error
System.Windows.Forms.MessageBox.Show(j.ToString());
That's not how C# works.
Your variable j is defined within the scope of the loop. That means it is not accessible from outside the loop.
See the documentation here.
If you want to be able to access the variable from outside the loop, you must declare it from outside the loop as well.
int j;
for (int i = 0; i < 4; i++)
{
j = 10;
}
You cant. its out of scope as you've defined it.
Imagine im the for loop, you gave me the job of doing it.. I think J=10 and make note of it a few times.. i then tell you i did the job.
you then try to use a thing I though of ..
You need to adjust the code.
int j=0;
for (int i = 0; i < 4; i++)
{
j = 10;
'print j within the loop, which works fine
System.Windows.Forms.MessageBox.Show(j.ToString());
}
System.Windows.Forms.MessageBox.Show(j.ToString());
now j exists, inside the loop because it already is made.. so I come and find a postit called "j" and I can write 10 on it. You come back after Im done, the postit is still there.
As you can clearly see the highest index of the args[] is 2, however the iterator somehow gets to 3.
Explanation?
Edit: The commented Thread.Sleep magically fixes the problem.
This is caused by i being declared outside of the for loop, for the entire for loop. Since there is no guarantee that the Thread is executed at that point, the value of i can change before the Thread executes. You can "resolve" this by declaring a local variable in the for loop.
//for loop ..
var localIndex = i;
var temp = new Thread(() => PrintOut(args[localIndex], IsFilePath(args[localIndex])));
temp.Start();
//for loop ..
EDIT: Also, can you please post a code snippet next time, saves me having to write out the code again :P
First of all
for (var i = 0; i< args.Length; i++)
{
}
is equivalent to:
int i = 0;
loop:
if (i < args.Length)
{
i++;
goto loop;
}
so you see that i is incremented to 3 in order to check your condition.
Of course, new Thread(() => PrintOut(args[i], IsFilePath(args[i]))) is never actually called when i is 3. However, since there is only one instance of i which the loop changes on each iteration, the execution that started when i was 2 is affected when i is incremented to 3.
To fix this, you need to copy the value of i into a new variable on each loop iteration. This copy will not be updated by the for loop:
for (var i = 0; i< args.Length; i++)
{
var copy = i;
Thread temp = new Thread(() => PrintOut(args[copy], IsFilePath(args[copy]))
}
Hi I have a problem with a for loop.
It looks like this
for (int i = 0; i < ObjectManager.Instance.Objects.Count; i++)
{
if (ObjectManager.Instance.Objects[i] is Asteroid)
{
ObjectManager.Instance.Objects.Remove(ObjectManager.Instance.Objects[i]);
}
}
But the count gets shorter while I remove objects, which causes the loop to end prematurely. Is there a way to do this without a bunch of extra loops.
Why don't you loop backward?
// Just change the order from Count - 1 down to 0
for (int i = ObjectManager.Instance.Objects.Count - 1; i >= 0; --i)
{
if (ObjectManager.Instance.Objects[i] is Asteroid)
{
ObjectManager.Instance.Objects.Remove(ObjectManager.Instance.Objects[i]);
}
}
In case you have to loop forward (e.g. if Instances should be deleted in the order they are created because they are depend on each other) you can modify for loop in this way:
for (int i = 0; i < ObjectManager.Instance.Objects.Count;) // <- No increment here
if (ObjectManager.Instance.Objects[i] is Asteroid)
ObjectManager.Instance.Objects.Remove(ObjectManager.Instance.Objects[i]);
else
i += 1; // <- Increment should be here!
Yet another possibility is Linq:
ObjectManager.Instance.Objects.RemoveAll(item => item is Asteroid);
Three options:
If ObjectManager.Instance.Objects is a List<T>, use List<T>.RemoveAll with a predicate, making your code much simpler:
// This replaces your whole loop...
ObjectManager.Instance.Objects.RemoveAll(x => x is Asteroid);
Count from the end of the collection rather than from the start, so that you don't need to adjust the index afterwards:
for (int i = ObjectManager.Instance.Objects.Count - 1; i >= 0; i--)
Just decrement i after calling Remove, so that you'll look at the right index on the next iteration.
Note that in the second and third options your code will be a lot simpler to read if you extract the expression ObjectManager.Instance.Objects into a local variable before you use it 4 times. Also consider using RemoveAt(i) rather than Remove(instances[i]), assuming RemoveAt is available for the type you're using.