I found a problem about the index on my script, why my index on method ChooseItem become 1 not 0?
here the script
public void AddBtn()
{
RemoveButton();
for (int i = 0; i < items.Count; i++)
{
indexer = i;
ShopItem activeOne = items[indexer];
go = Instantiate(prefabBtn, parentBtn);
btn = go.GetComponent<_button>();
btn.indexer = indexer;
btn.TheItems.sprite = items[indexer].theitem;
btn.button.onClick.AddListener(() =>chooseItem(indexer));
}
}
public void chooseItem(int index)
{
Debug.Log(index);
}
I don't know the problem, can anyone explain to me why? here the pict
Quite typical mistake when using lambdas!
You overwrite a field value
indexer = i;
Then this line
btn.button.onClick.AddListener(() =>chooseItem(indexer));
or better said the listener lambda expression
()=>chooseItem(indexer);
is executed lazy/delayed using the indexer field reference, not the current value! It will always use the latest value of the field indexer which in your case is 1 if there are two buttons in total.
This is called capture of outer variables:
Lambdas can refer to outer variables. These are the variables that are in scope in the method that defines the lambda expression, or in scope in the type that contains the lambda expression. Variables that are captured in this manner are stored for use in the lambda expression even if the variables would otherwise go out of scope and be garbage collected.
Same would happen btw if you directly used i.
What you need to do is assign the value to a local variable and use that one instead like
var currentIndex = i;
...
btn.button.onClick.AddListener(() =>chooseItem(currentIndex));
Or alternatively if you assign the value anyway to btn.indexer you probably could directly go
btn.button.onClick.AddListener(() => chooseItem(btn.indexer));
You need this code:
public void AddBtn()
{
RemoveButton();
for (int i = 0; i < items.Count; i++)
{
indexer = i;
ShopItem activeOne = items[indexer];
go = Instantiate(prefabBtn, parentBtn);
btn = go.GetComponent<_button>();
btn.indexer = indexer;
btn.TheItems.sprite = items[indexer].theitem;
btn.button.onClick.AddListener(() =>chooseItem(btn));
}
}
public void chooseItem(_button_ btn)
{
Debug.Log(btn.indexer);
}
Related
I have a few parameters from a different class AnalogGlitch, which I want to assign within this code. Instead of assigning values to them individually, I want to pack them in a list and to this in a loop.
However from the output I can see that the values of the variables to not change at all. I have not used as much C# before and wonder if this is something specific to the language (and not in python for example)?
The code works finde, when I assign values to the variables individually instead of in a loop ...
public class SlenderInView : MonoBehaviour
{
private AnalogGlitch glitch;
private bool isLooking = false;
private List<float> controls = new List<float>();
void Start()
{
glitch = GetComponent<AnalogGlitch>();
controls.Add(glitch.scanLineJitter);
controls.Add(glitch.horizontalShake);
controls.Add(glitch.colorDrift);
}
void Update()
{
// removed the code dealing with isLooking
if (isLooking)
{
for(int i=0; i<controls.Count; i++)
{
if(controls[i] < 1)
{
controls[i] += (float)0.1;
}
}
}
else
{
for (int i = 0; i < controls.Count; i++)
{
controls[i] += 0;
}
}
}
}
You're adding the values of the variables to the list - you're not adding the variables themselves. So when you change the content of the list, that won't affect the variables at all. As a simpler example:
int x = 10;
List<int> values = new List<int>();
values.Add(x); // Add the *current value of x* to the list
values[0] = 20; // This doesn't change the value of x
Console.WriteLine(x);
So the behaviour you're seeing is definitely what I'd expect. There's no easy way of achieving the behaviour you want without bigger changes. You could write something like an Int32Holder class, and change the AnalogGlitch variables to be of type Int32Holder, then create a List<Int32Holder>, etc... but I suspect it would be simpler just to hard-code the use of the variables:
glitch.scanLineJitter = MaybeUpdate(glitch.scanLineJitter);
glitch.horizontalShake = MaybeUpdate(glitch.horizontalShake);
glitch.colorDrift = MaybeUpdate(glitch.colorDrift);
Or pass the variables by reference to a method:
MaybeUpdate(ref glitch.scanLineJitter);
MaybeUpdate(ref glitch.horizontalShake);
MaybeUpdate(ref glitch.colorDrift);
Your variables glitch.scanLineJitter, glitch.horizontalShake and glitch.colorDrift are value types, meaning that when you add them to your list, you actually create a copy of the value they hold. Writing
controls[i] += (float)0.1;
does not change the original variable, it just changes the value contained inside the list, which was a copy of the original.
A hw was given to us to change a previous hw in C# which used 2d arrays and instead of using 2d arrays we use an Array list with variables declared in an object called Students.
I would like to use a method to calculate a student best mark; however, the method is giving me an error and a warning which are the following:
Error:
CS0161 'Form1.Calc_HighestMarkOutput(int)': not all code paths return a value.
Warning:
CS0162 Unreachable code detected.
Inside the arraylist the user inputed (through use of an overload constructor):
Student Name, Maths Mark, English Mark, Maltese Mark, Email Address.
and since in the method I am returning 3 highest marks in 3 subjects attained by all students, I decided to return an array. which will be accessed by a temporary array inside the main program by selectedindex.
Please help me find the problem.
And thanks in advance.
public int[] Calc_HighestMarkOutput(int HighestMarkIndex)
{
int[] HighestMarkOutput = new int[3];
int HighestMarkMaths = 0;
int HighestMarkEnglish = 0;
int HighestMarkMaltese = 0;
int TMPHighestMarkMaths = 0;
int TMPHighestMarkEnglish = 0;
int TMPHighestMarkMaltese = 0;
for (int i = 0; i < myStudents.Count; i++) //a loop through an array list.
{
if (myStudents[HighestMarkIndex].Maths_Result > HighestMarkMaths)
{
TMPHighestMarkMaths = myStudents[HighestMarkIndex].Maths_Result;
HighestMarkMaths = TMPHighestMarkMaths;
}
if (myStudents[HighestMarkIndex].English_Result > HighestMarkEnglish)
{
TMPHighestMarkEnglish = myStudents[HighestMarkIndex].English_Result;
HighestMarkEnglish = TMPHighestMarkEnglish;
}
if (myStudents[HighestMarkIndex].Maltese_Result > HighestMarkMaltese)
{
TMPHighestMarkMaltese = myStudents[HighestMarkIndex].Maltese_Result;
HighestMarkMaltese = TMPHighestMarkMaltese;
}
HighestMarkOutput[0] = HighestMarkMaths;
HighestMarkOutput[1] = HighestMarkEnglish;
HighestMarkOutput[2] = HighestMarkMaltese;
return HighestMarkOutput;
}
You are getting an error, because the return-statement is inside the loop. If the list is empty, the return statement will never be executed. Also, you know the result only after the loop has finished. So, place the return-statement after the loop.
Since the purpose of this method is to find the highest marks, it makes no sense to pass such an index into the routine as a parameter.
Using foreach is easier than for because you don't have to deal with indexes.
Instead of returning an array, return an unnamed student containing the results. You can drop useless temporary variables.
public Student Calc_HighestMarkOutput()
{
var result = new Student(); // You also might have to add a default constructor.
foreach (Student student in myStudents) {
if (student.Maths_Result > result.Maths_Result) {
result.Maths_Result = student.Maths_Result;
}
if (student.English_Result > result.English_Result) {
result.English_Result = student.English_Result;
}
if (student.Maltese_Result > result.Maltese_Result) {
result.Maltese_Result = student.Maltese_Result;
}
}
return result;
}
You could also use Math.Max to simplify finding the maximum value
foreach (Student student in myStudents) {
result.Maths_Result = Math.Max(result.Maths_Result, student.Maths_Result);
result.English_Result = Math.Max(result.English_Result, student.English_Result);
result.Maltese_Result = Math.Max(result.Maltese_Result, student.Maltese_Result);
}
With these refactorings, the method shrinks from 22 lines (not counting empty lines and lines containing only a brace) to 7 lines.
could you explain me one example with delegate and lambda expression
List<Func<int>> list = new List<Func<int>>();
for (int i = 0; i < 10; i++)
{
list.Add(() => i);
}
foreach (var func in list)
{
Console.WriteLine(func());
}
I understand, that I have List of refers to methods whithout params and returns Int, but why it returns 10 times max value from loop? How it works? Thx!
It is closure when you do:
(() => i)
The lambda gets the original variable i, not a copy, so you get 10 ten times - because on calling delegate the original value of i is 10 (after loop)
If you change code and add local temp variable you will get 0 1 2 3 4 5 6 7 8 9:
for (int i = 0; i < 10; i++)
{
int j = i;
list.Add(() => j);
}
You can read about closures here:
Closures
When you pass variable inside of delegate's method it is its link, and not it's value that is used inside the delegate.
We create list of functions:
List<Func<int>> list = new List<Func<int>>();
Here we initialize list with functions and every function should use reference to memory where i variable is stored when it's fired :
for (int i = 0; i < 10; i++)
{
list.Add(() => i);
}
Now it is time to fire each function but at this time for loop is already finished executing and i variable holds its final value of 10. Remember that delegate can always find parameter because it holds reference to it. It could not be garbage collected :
foreach (var func in list)
{
// by the time we do it it has value of 10
Console.WriteLine(func());
}
The reason for that is that the lambda () => i captures the local variable i. This means that i will not be evaluated when it is added to the list but when you actually invoke the lambda with ().
At the time this happens in your code (Console.WriteLine(func());) the value of i is already 10 because the for loop has finished.
If you want to avoid this behaviour you have to copy the value of i into a local variable that will not change after the lambda has been created.
for (int i = 0; i < 10; i++)
{
int tmp = i;
list.Add(() => tmp);
}
Actually the lambda expression is a delegate, and you calling it after the loop ends and at that time i has value 10, so when the delegates are getting called, they all have i value 10 as same copy is used due to closure, you will need to write as #Roma suggested for it to work as you expect it to, otherwise it will,
this :
for (int i = 0; i < 10; i++)
{
list.Add(() => i);
}
can be looked as :
int i;
for (i=0; i < 10; i++)
{
list.Add(() => i);
}
This question already has answers here:
Using the iterator variable of foreach loop in a lambda expression - why fails?
(3 answers)
Closed 8 years ago.
I got this piece of code,
delegate void Printer();
static void Main(string[] args)
{
List<Printer> printers = new List<Printer>();
for (int i = 0; i < 10; i++)
{
printers.Add(delegate { Console.WriteLine(i); });
}
foreach (Printer printer in printers)
{
printer();
}
Console.ReadLine();
}
Here the output is '10' for ten times.
The scope of i is with in the for loop. But while we retrieve in out side that we are still getting value from i.
How is it possible?
You have modified closure. Try this:
for (int i = 0; i < 10; i++)
{
int ii = i;
printers.Add(delegate { Console.WriteLine(ii); });
}
When you use in your anonymous method access the the variable in you local scope it creates closure.
The code in the delegate is not run until it is called, which happens in the second loop. It then refers to the i which was defined within the scope of the first loop, but with it's current value - and since the first loop has been completed already, i will be 10 each time.
I believe each of the delegates you create are given the same scope as the first loop, if that makes sense. This means that each i has it's delegate as it's scope, and since each delegate is defined within the scope of the first loop, each i will also have the loop as it's scope, even if the delegate logic is called outside that scope, as in your example.
Since i is valid throughout / across several iterations of the loop, it gets updated, and is always 10 by the time the delegates get called.
This explains why the following works as a fix:
for(int i = 0; i < 10; i++)
{
var localVar = i; // Only valid within a single iteration of the loop!
printers.Add(delegate { Console.WriteLine(localVar); });
}
Let's unroll the loop:
int i=0;
printers.Add(delegate { Console.WriteLine(i); })
i=1;
printers.Add(delegate { Console.WriteLine(i); })
...
i=10;
printers.Add(delegate { Console.WriteLine(i); })
As you can see the i variable is captured within the delegate, and the delegate itself is not run until the loop ends, and the variable has achieved the last value (10).
A simple workaround is to assign the loop variable to a local helper variable
for (int i = 0; i < 10; i++)
{
var index = i;
printers.Add(delegate { Console.WriteLine(index); });
}
As for the scope issue, any captured variables have their scope (and lifetime) extended. A variable used within a lambda/delegate will not be garbage collected until the delegate itself goes out of scope - which can be a problem for large objects. Specifically, section, 7.15.5.1 of the C# 5 Specification states:
When an outer variable is referenced by an anonymous function, the
outer variable is said to have been captured by the anonymous
function. Ordinarily, the lifetime of a local variable is limited to
execution of the block or statement with which it is associated
(ยง5.1.7). However, the lifetime of a captured outer variable is
extended at least until the delegate or expression tree created from
the anonymous function becomes eligible for garbage collection.
Each delegate is only invoked in the foreach, after the for loop. By this time, the variable i captured by the closure is already at its final value, viz 10. You can solve like so:
for (int i = 0; i < 10; i++)
{
var cache = i;
printers.Add(delegate { Console.WriteLine(cache); });
}
I have a fairly generic class (Record) that I have added a callback handler to, so I can do something like the following.
record.Save(AfterSaveMethod);
Which also returns the identity number of the record created. The issue I have now is that I have this nested save routine in a loop, and I need to use/pass the i variable!
for (int i; i < count ;i++)
{
record.Save(AfterSaveMethod2) //but I need to pass i through as well
}
What do I do here?
A\ rewrite the save method and include it in this class (yuk),
B\ have an overloaded save option that takes an object as a parameter, so I can pass it through to my record class, and then merge it with the identity number. Returning both the identity number and anything extra contained in the passed through object (hmm, sounds a bit messy),
C\ is there a sexier option?
This is where anonymous methods or lambda expressions are really handy :)
Try this (assuming C# 3):
for (int i = 0; i < count; i++)
{
int index = i;
record.Save(id => AfterSaveMethod2(id, index));
}
Note that the lambda expression here is capturing index, not i - that's to avoid the problems inherent in closing over the loop variable.
You could also create a (thread-static) context object, which stores your "state" (in this case, the index variable value), which you can access using a static property (say MyContext.Current). Depends on the complexity of the context ...
WCF uses something like this with OperationContext.Current, ASP.NET, TransactionScope et al.
If the context is a bit more complex than yours and you don't want to pollute the signatures of several methods, I think this model is quite neat.
Use:
// init context
MyContext.Current = new MyContext();
for (var idx = 0; idx < 99; idx++) {
MyContext.Current.Idx = idx;
record.Save(AfterSaveMethod);
}
// uninitialize context
MyContext.Current = null;
(very simplistic sample)
If the context is [ThreadStatic] you could have multiple concurrent calls which won't affect each other
class SexierSaveMethod
{
public Int32 Index { get; set; }
public SexierSaveMethod(Int32 i)
{
Index = i;
}
public void AfterSaveMethod()
{
// Use i here
}
}
record.Save(new SexierSaveMethod(i).AfterSaveMethod);