This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed 6 years ago.
I am trying to pass an integer value into a ElapsedEventHandler so I can do logic based on the integer passed in. However, when the Event is raised, the value coming in is not the value I initialized it to. I may not be understanding completely how the delegate works.
class Example
{
Dictionary<int, Timer> timer;
public Example()
{
timer = new Dictionary<int, timer>();
for(int i = 0; i < 12; ++i)
{
timer.Add(i, new Timer(5000));
timer[i].Elapsed += delegate { TimerTickCustom(i); };
}
}
public void Process() // called each cycle
{
for(int i = 0; i < 12; ++i)
{
timer[i].Start();
}
}
private void TimerTickCustom(int i)
{
// The value of i coming in does not match the dictionary key.
}
}
It depends on where the local value i is for the delegate to regard it as "specific" to it's scope. As i is defined outside the loop, the delegate doesn't define it's own "copy" of this variable - as all the delegates expect the same i.
What you need to do is assign it to a variable that's "on the same level" as the delegate itself.
Not sure if I'm using the right language here to explain it. But this should work:
class Example
{
Dictionary<int, Timer> timer;
public Example()
{
timer = new Dictionary<int, Timer>();
for(int i = 0; i < 12; ++i)
{
int iInScopeOfDelegate = i;
timer.Add(i, new Timer(5000));
timer[i].Elapsed += delegate { TimerTickCustom(iLocalToDelegate ); };
}
}
public void Process() // called each cycle
{
for(int i = 0; i < 12; ++i)
{
timer[i].Start();
}
}
private void TimerTickCustom(int i)
{
// The value of i coming in does not match the dictionary key.
}
}
There's some interesting discussion if you know which words to type into the search engine (which of course, you can't really know until someone tells you).
Related
I see that there is a similar question for C++. Does anyone know why this method works when the method is non-generic, but as soon as I make it generic, the random number portion of code fails?
Error: Cannot implicitly convert type int to 'T'. If I can't use generics, I will have to rewrite the same function over and over for each different length of array.
public void fillGenericArray<T>(T[] inputArray) where T : IComparable
{
var randomNumb1 = new Random();
for (int i = 0; i < inputArray.Length - 1; i++)
{
Console.WriteLine($"{inputArray[i] = randomNumb1.Next(1, 501)},");
}
}
I had to look twice at this, but here's the issue:
Because inputArray is an 'array of type T'
then even though i is an int the expression
inputArray[i]
returns a type T not a type int.
And so, conversely, a type T must be assigned to it.
A generic method like this might achieve your goal:
public static void fillGenericArray<T>(T[] inputArray)
{
for (int i = 0; i < inputArray.Length; i++)
{
// Where T has a CTor that takes an int as an argument
inputArray[i] = (T)Activator.CreateInstance(typeof(T), Random.Next(1, 501));
}
}
(Thanks to this SO post for refreshing my memory about instantiating T with arguments.)
You could also use Enumerable.Range() to get the same result without writing a method at all:
// Generically, for any 'SomeClass' with a CTor(int value)
SomeClass[] arrayOfT =
Enumerable.Range(1, LENGTH).Select(i => new SomeClass(Random.Next(1, 501)))
.ToArray();
(Slightly Modified with help from this SO post) - see the answer using Enumerable.Range().
Here is a test runner:
class Program
{
static Random Random { get; } = new Random();
const int LENGTH = 10;
static void Main(string[] args)
{
Console.WriteLine();
Console.WriteLine("With a generic you could do this...");
SomeClass[] arrayOfT;
arrayOfT = new SomeClass[LENGTH];
fillGenericArray<SomeClass>(arrayOfT);
Console.WriteLine(string.Join(Environment.NewLine, arrayOfT.Select(field=>field.Value)));
Console.WriteLine();
Console.WriteLine("But perhaps it's redundant, because Enumerable is already Generic!");
arrayOfT = Enumerable.Range(1, LENGTH).Select(i => new SomeClass(Random.Next(1, 501))).ToArray();
Console.WriteLine(string.Join(Environment.NewLine, arrayOfT.Select(field => field.Value)));
// Pause
Console.WriteLine(Environment.NewLine + "Any key to exit");
Console.ReadKey();
}
public static void fillGenericArray<T>(T[] inputArray)
{
for (int i = 0; i < inputArray.Length; i++)
{
inputArray[i] = (T)Activator.CreateInstance(typeof(T), Random.Next(1, 501));
}
}
class SomeClass
{
public SomeClass(int value)
{
Value = value;
}
public int Value { get; set; }
}
}
Clone or Download this example from GitHub.
There is no reason to use generics. Just replace T with int and you will have function that does what you want (based on your question and comment below it).
EDIT: From your comment it seems you misunderstand the purpose of generics. The non-generic function WILL work for all lengths of the array.
And to answer why the change to generics fails. You are trying to assign int to generic type T which can be anything and compiler will not allow such a cast.
This question already has answers here:
C#: Use of unassigned local variable, using a foreach and if
(6 answers)
Closed 4 years ago.
I am getting an error for dateClose.closing, "use is unassigned local variable". I declared dateClose outside of the for loop and defined the value inside the for loop. How can I make that value available outside of the for loop?
public class SMA
{
public Models.DateClose SMAMethod (Queue<Models.DateClose> queue, int period)
{
decimal average, sum=0;
Models.DateClose dateClose;
for (int i = 0; i < period; i++)
{
dateClose = queue.Dequeue();
sum += dateClose.Close;
}
average = sum/period;
dateClose.Close = average; <--- error
return dateClose;
}
}
you can simply fix the error by doing
Models.DateClose dateClose = null;
however you would also want to add a null check to make sure you don't run into null ref exception if queue has no item.
You can do this. If your period variable is greater than the queue count than dateClose.Close will throw an exception.
public Models.DateClose SMAMethod (Queue<Models.DateClose> queue, int period)
{
decimal average, sum=0;
Models.DateClose dateClose = null;
for (int i = 0; i < period; i++)
{
dateClose = queue.Dequeue();
if(dateClose != null)
sum += dateClose.Close;
}
average = sum/period;
dateClose.Close = average;
return dateClose;
}
Why do you get this error:
if you have the class, member variables neednot be initialized:
public class Test
{
private int temp; // this is okay.
..
}
However, if you have a local variable, then you need to initialize them:
public void Method()
{
int variabl;
sum += variable; // error.
}
So, local variables need to be initialized but member variables neednt be.
I tried to use delegate chain like below, trying to make animation in unity:
public class Class1
{
class Dele {
delegate void MyDelegate();
private MyDelegate dele;
private int count = 0;
public void Animate() {
dele = new MyDelegate(DoIe);
}
IEnumerator Ie() {
Debug.Log(count);
count += 1;
yield return new WaitForSeconds(5f);
}
private void DoIe() {
StartCouroutine(Ie());
for (int i=0; i<10; i++) {
dele += DoIe;
}
dele();
}
}
//call new Dele().Animate() here
}
I thought the Log will go like
1
(5 secs)
2
(5 secs)
...
10
but instead,
1
2
..
10
was logged at the same time.
If I want to callback another Ie after 5 second,
what should I do??
With coroutines it's the code inside the routine (the IEnumerator method) that runs later. The code after StartCoroutine() in your void-returning method above will run synchronously (straight away), like you saw.
You don't need a delegate here at all. All you need is this:
IEnumerator Ie() {
for (int i=0; i<10; i++) {
Debug.Log(count);
count += 1;
yield return new WaitForSeconds(5f);
}
}
private void DoIe() {
StartCoroutine(Ie());
}
First of all, your class needs to inherit from MonoBehavious for StartCoroutine to work.
Then in regards of your question: you need to start the coroutine with a delay, just adding them to a multicast delegate is simply not doing what you think you are
This question already has answers here:
Regarding local variable passing in Thread
(2 answers)
Captured variable in a loop in C#
(10 answers)
(Sometimes) The given key was not present in the dictionary
(2 answers)
Closed 5 years ago.
I am trying to pass more than one parameter to a thread.
I am only testing with one parameter though, and the Lambda Expression is not passing the valeis of the parameters correctly. The ParameterizedThreadStart is working correctly, but I can only pass one object variable and not more and this limits me.
I have made an example for each and the outputs of the Lambda Expression method outputs are incorrect.
For both cases, numOfPortThreads = 2
Using ParameterizedThreadStart
public void InitializePorts(int numOfPortThreads)
{
Thread[] tPortArr = new Thread[numOfPortThreads];
for (int i = 0; i < numOfPortThreads; i++)
{
tPortArr[i] = new Thread(new ParameterizedThreadStart(new PortSim().PortRun));
tPortArr[i].Start(i);
}
}
In "PortSim.cs"
public void PortRun(object portID)
{
portStopWatch.Start();
Console.WriteLine("This is Port {0}", portID);
Console.ReadKey();
}
The output in that case is:
This is Port 0
This is Port 1
However, using Lambda Expression,
public void InitializePorts(int numOfPortThreads)
{
Thread[] tPortArr = new Thread[numOfPortThreads];
for (int i = 0; i < numOfPortThreads; i++)
{
tPortArr[i] = new Thread( () => new PortSim().PortRun(i));
tPortArr[i].Start();
}
}
In "PortSim.cs"
public void PortRun(int portID)
{
portStopWatch.Start();
Console.WriteLine("This is Port {0}", portID);
Console.ReadKey();
}
The output in that case is:
This is Port 2
This is Port 2
What is wrong with the second example? Why does it yield incorrect results?
You need to introduce local variable like this.
public void InitializePorts(int numOfPortThreads)
{
Thread[] tPortArr = new Thread[numOfPortThreads];
for (int i = 0; i < numOfPortThreads; i++)
{
int j = i;
tPortArr[j] = new Thread( () => new PortSim().PortRun(j));
tPortArr[j].Start();
}
}
you may be wondering why just google closures in c#
In the following program, DummyMethod always print 5. But if we use the commented code instead, we get different values (i.e. 1, 2, 3, 4). Can anybody please explain why this is happenning?
delegate int Methodx(object obj);
static int DummyMethod(int i)
{
Console.WriteLine("In DummyMethod method i = " + i);
return i + 10;
}
static void Main(string[] args)
{
List<Methodx> methods = new List<Methodx>();
for (int i = 0; i < 5; ++i)
{
methods.Add(delegate(object obj) { return DummyMethod(i); });
}
//methods.Add(delegate(object obj) { return DummyMethod(1); });
//methods.Add(delegate(object obj) { return DummyMethod(2); });
//methods.Add(delegate(object obj) { return DummyMethod(3); });
//methods.Add(delegate(object obj) { return DummyMethod(4); });
foreach (var method in methods)
{
int c = method(null);
Console.WriteLine("In main method c = " + c);
}
}
Also if the following code is used, I get the desired result.
for (int i = 0; i < 5; ++i)
{
int j = i;
methods.Add(delegate(object obj) { return DummyMethod(j); });
}
The problem is that you're capturing the same variable i in every delegate - which by the end of the loop just has the value 5.
Instead, you want each delegate to capture a different variable, which means declaring a new variable in the loop:
for (int i = 0; i < 5; ++i)
{
int localCopy = i;
methods.Add(delegate(object obj) { return DummyMethod(localCopy); });
}
This is a pretty common "gotcha" - you can read a bit more about captured variables and closures in my closures article.
This article will probably help you understand what is happening (i.e. what a closure is): http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx
If you look at the code generated (using Reflector) you can see the difference:
private static void Method2()
{
List<Methodx> list = new List<Methodx>();
Methodx item = null;
<>c__DisplayClassa classa = new <>c__DisplayClassa();
classa.i = 0;
while (classa.i < 5)
{
if (item == null)
{
item = new Methodx(classa.<Method2>b__8);
}
list.Add(item);
classa.i++;
}
foreach (Methodx methodx2 in list)
{
Console.WriteLine("In main method c = " + methodx2(null));
}
}
When you use the initial code it creates a temporary class in the background, this class holds a reference to the "i" variable, so as per Jon's answer, you only see the final value of this.
private sealed class <>c__DisplayClassa
{
// Fields
public int i;
// Methods
public <>c__DisplayClassa();
public int <Method2>b__8(object obj);
}
I really recommend looking at the code in Reflector to see what's going on, its how I made sense of captured variables. Make sure you set the Optimization of the code to ".NET 1.0" in the Option menu, otherwise it'll hide all the behind scenes stuff.
I think it is because the variable i is put to the heap (it's a captured variable)
Take a look at this answer.