C# Initialize local variable once - c#

I assume my question will be totally stupid but I have to know the answer.
Is it possible to initialize a variable just once in this situation?
static void Main()
{
while (true)
{
MethodA();
MethodB();
}
}
private static void MethodA()
{
string dots = string.Empty; // This should be done only once
if (dots != "...")
dots += ".";
else
dots = string.Empty;
Console.WriteLine(dots + "\t" + "Method A");
System.Threading.Thread.Sleep(500);
}
private static void MethodB()
{
string dots = string.Empty; // This should be done only once
if (dots != ".....")
dots += ". ";
else
dots = string.Empty;
Console.WriteLine(dots + "\t" + "Method B");
System.Threading.Thread.Sleep(500);
}
Of course I can initialize string dots out of method but I don't want to do mess in the code, and this can't be done in any other loop too (like for). Any ideas how solve this or am I so stupid to think normally?
Thanks in advance.
EDIT: I've changed the sample code to be more practical. Desired output should be:
. Method A
. Method B
.. Method A
.. Method B
... Method A
... Method B
Method A
.... Method B
. Method A
.....Method B
Etc. etc.

You said you don't want to keep the dots out side of Method (in the Method's class), then you must return the value from Method so that you can at least pass it in later on, thus persisting its state.
string Method(string dot = string.Empty)
{
if(dot == "...") return string.Empty;
else return dot + ".";
}
var result = Method(Method(Method(Method(Method(Method()))))) // etc...
EDIT:
Your edited question does not make your initial problem more practical. It still suffers from the same problem: You want X but C# does not have X. Use C++ or VB.NET instead.
The answer to your question
"Is it possible to initialize a variable just once in this situation?"
is Sorry, NO!

You could keep dots in your class, and initialize it when the class is created, i.e.
string dots = string.Empty;
private void Method()
{
if (dots != "...")
dots += ".";
else
dots = string.Empty;
}

In other languages (C++) you can declare static variables at any scope. This would solve your issue neatly but C# doesn't permit declaration of static variables within functions. This is actually a C# FAQ: Why doesn't C# support static method variables?.
This design feature means that you cannot do what you want in a conventional method. You might be able to do something particularly cunning with currying, but if you don't want to mess with the existing program structure, that's out. I'm sure that there's a way to write native code that would get you to what you want, but it feels like a bad idea.
Putting it simply, you're asking for data that persists outside of its scope. In C#, your chief (sole?) remedy is to increase that data's scope.

You are looking for persistent data between calls to the method, so you need a data element outside of your call. You don't have static local variables in C#.
Consider reading this Stackoverflow post.

Are you thinking of a static local variable as in C++? They are not supported in C#, as discussed here.

Related

C# How do I make my loop not loop back to the first line?

I have been working on a text adventure for a while, but I am having an issue. I have a tutorial, but I want to know how when the user types something that isn't listed, instead of asking the question again, just displaying the option. My example here is a tutorial option that isn't available yet, so they have to type back. I want it so if they type anything other then back, it just displays [Back] and not "That option isn't listed.". In the code example I have tried to use more than one static void because maybe I couldn't reference my own void but that wouldn't work, And I separated the writing and the back option into two but it still doesn't work. When the code example has SW(60, "text") that is just a custom thing for slow write and the 60 is 60 millisecond delay. So, whenever I run it, and type something other then back it will say "That option isn't listed.", like planed but then somehow clear the line and tell you the settings are disabled.
Here is my code.
'''
static string TutorialBack;
public static void TutorialLine()
{
Console.Clear();
SW(40, "The tutorial is currently being devolped, thank you for your patience and support.");
Game.TutorialOof();
}
public static void TutorialOof()
{
Game.Tutorial();
}
static void Tutorial()
{
Thread.Sleep(1600);
Console.WriteLine();
SW(40, "[Back]");
Console.WriteLine();
TutorialBack = Console.ReadLine();
if (string.Equals(TutorialBack, "back", StringComparison.CurrentCultureIgnoreCase))
{
Console.Clear();
Game.Menue();
}
else
{
SW(60, "That option isn't listed.");
Game.TutorialOof();
}
Thanks for any help.
Text adventures are still alive? ;)
I think you should use the var to check equals not the type (string).
// ...
if (TutorialBack.Equals("Back", StringComparison.CurrentCultureIgnoreCase))
{
Console.Clear();
Game.Menue();
}
// ...
Edit: Sorry if I misunderstood what you mean. If you want keep the current just quote out the instruction inside else.
else
{
//SW(60, "That option isn't listed.");
//Game.TutorialOof();
}
Or
else
{
//SW(60, "That option isn't listed.");
Game.TutorialOof();
}

MessageBox and while loop C#

I'm modifying existing C# code in order to pilote a piston. Every 30ms, I have a direct feedback of the position of this piston, through an event. The value is stored in a global variable I use to get the current position of the piston.
What I'm trying to achieve: for a given distance input (A->C), I want the piston to travel at full speed for 95% of the distance (A->B), and then slower for the remaining 5% (B->C).
I have access to a command that defines the speed and the destination of the piston : pos(velocity, destination).
However, if I write that code:
pos(fullSpeed,B);
pos(reducedSpeed, C);
the piston directly goes from fullSpeed to reducedSpeed
I tried to use a while loop to compare the current position of the piston with the goal destination, however, upon entering the while loop, the variable storing the piston position does not update anymore.
However, I noticed that by throwing a MessageBox in between, the position value keeps on getting updated, and I can simply click "ok" to launch the second command.
pos(fullSpeed,B);
MessageBox.show("Wait");
pos(reducedSpeed, C);
I would like to know why the "while" loop stops the update of the position variable but the MessageBox does not. I mean, as long as I don't click the "ok" button, the box is here preventing me from doing anything, which for me ressembles a while loop behaviour. Is there another way for me to do this instead of the MessageBox ?
I have little to no knowledge when it comes to C# and no support. I have tried to look in the documentation, but I did not find an answer (I have probably missed it). Any lead is more than welcome.
EDIT: I have no documentation for that code, and it is barely commented. Here is what I gathered (really hope it helps):
To move the piston, taht function is called:
MyEdc.Move.Pos(control, speed, destination, ref MyTan);
control simply define what we pilote (a distance or a load, it is an enum), and I have no idea what MyTan does. Only thing I know is that the MyEdc.Move.Pos returns an error code.
If I look at the definition of "pos", I am redirected to class
public DoPEmove Move;
containing among other things:
public DoPE.ERR Pos(DoPE.CTRL MoveCtrl, double Speed, double Destination, ref short Tan);
DoPE.ERR is also an type enum. However, I cannot reach the definition of a function named "Pos". Coud it be within the .dll included ?
The following is the code that allows me to access the position of the piston (without the global variables):
private int OnData(ref DoPE.OnData Data, object Parameter)
{
if (Data.DoPError == DoPE.ERR.NOERROR)
{
DoPE.Data Sample = Data.Data;
Int32 Time = Environment.TickCount;
if ((Time - LastTime) >= 300 /*ms*/)
{
LastTime = Time;
string text;
text = String.Format("{0}", Sample.Time.ToString("0.000"));
guiTime.Text = text;
text = String.Format("{0}", Sample.Sensor[(int)DoPE.SENSOR.SENSOR_S].ToString("0.000"));
guiPosition.Text = text;
text = String.Format("{0}", Sample.Sensor[(int)DoPE.SENSOR.SENSOR_F].ToString("0.000"));
guiLoad.Text = text;
text = String.Format("{0}", Sample.Sensor[(int)DoPE.SENSOR.SENSOR_E].ToString("0.000"));
guiExtension.Text = text;
}
}
return 0;
}
Which is called using
MyEdc.Eh.OnDataHdlr += new DoPE.OnDataHdlr(OnData);
I realise how little I know on how the soft operates, and how frustrating this is for you. If you think this is a lost cause, no problem, I'll try Timothy Jannace solution, and if it does not help me, I'll stick with the MessageBox solution. I just wanted to know why the MessageBox allowed me to sort of achieve my objectif, but the while loop did not, and how to use it in my advantage here.
I tried to use a while loop to compare the current position of the
piston with the goal destination, however, upon entering the while
loop, the variable storing the piston position does not update
anymore.
While you are in the while loop, your app can no longer receive and process the feedback event.
One possible solution would be to use async/await like this:
private const int fullSpeed = 1;
private const int reducedSpeed = 2;
private int currentPistonPositon = 0; // global var updated by event as you described
private async void button1_Click(object sender, EventArgs e)
{
int B = 50;
int C = 75;
pos(fullSpeed, B);
await Task.Run(() =>
{ // pick one below?
// assumes that "B" and "currentPistonPosition" can actually be EXACTLY the same value
while (currentPistonPositon != B)
{
System.Threading.Thread.Sleep(25);
}
// if this isn't the case, then perhaps when it reaches a certain threshold distance?
while (Math.Abs(currentPistonPositon - B) > 0.10)
{
System.Threading.Thread.Sleep(25);
}
});
pos(reducedSpeed, C);
}
Note the button1_Click method signature has been marked with async. The code will wait for the while loop inside the task to complete while still processing event messages because of the await. Only then will it move on to the second pos() call.
Thank you for your answer ! It works like a charm ! (good catch on the
EXACT value). I learnt a lot, and I am sure the async/await combo is
going to be very usefull in the future ! – MaximeS
If that worked well, then you might want to consider refactoring the code and making your own "goto position" method like this:
private void button1_Click(object sender, EventArgs e)
{
int B = 50;
int C = 75;
GotoPosition(fullSpeed, B);
GotoPosition(reducedSpeed, C);
}
private async void GotoPosition(int speed, int position)
{
pos(speed, position);
await Task.Run(() =>
{
while (Math.Abs(currentPistonPositon - position) > 0.10)
{
System.Threading.Thread.Sleep(25);
}
});
}
Readability would be greatly improved.
You could even get fancier and introduce a timeout concept into the while loop. Now your code could do something like below:
private void button1_Click(object sender, EventArgs e)
{
int B = 50;
int C = 75;
if (GotoPosition(fullSpeed, B, TimeSpan.FromMilliseconds(750)).Result)
{
if (GotoPosition(reducedSpeed, C, TimeSpan.FromMilliseconds(1500)).Result)
{
// ... we successfully went to B at fullSpeed, then to C at reducedSpeed ...
}
else
{
MessageBox.Show("Piston Timed Out");
}
}
else
{
MessageBox.Show("Piston Timed Out");
}
}
private async Task<bool> GotoPosition(int speed, int position, TimeSpan timeOut)
{
pos(speed, position); // call the async API
// wait for the position to be reached, or the timeout to occur
bool success = true; // assume we have succeeded until proven otherwise
DateTime dt = DateTime.Now.Add(timeOut); // set our timeout DateTime in the future
await Task.Run(() =>
{
System.Threading.Thread.Sleep(50); // give the piston a chance to update maybe once before checking?
while (Math.Abs(currentPistonPositon - position) > 0.10) // see if the piston has reached our target position
{
if (DateTime.Now > dt) // did we move past our timeout DateTime?
{
success = false;
break;
}
System.Threading.Thread.Sleep(25); // very small sleep to reduce CPU usage
}
});
return success;
}
If you're using events you are probably having concurrency issues. Especially with events being raised every 30ms!
A very simple way to handle concurrency is to use a lock object to prevent different threads from using contested resources simultaneously:
class MyEventHandler
{
private object _lockObject;
MyEventHandler()
{
_lockObject = new object();
}
public int MyContestedResource { get; }
public void HandleEvent( object sender, MyEvent event )
{
lock ( _lockObject )
{
// do stuff with event here
MyContestedResource++;
}
}
}
Keep in mind that is very simple and by no means perfect in every scenario. If you provide more information about how the events are raised and what you're doing with them people will be able to provide more help.
EDIT:
Using that signature you posted for the Pos method I was able to find documentation on the library you are using: https://www.academia.edu/24938060/Do_PE
The reason you only see the method signature when you goto definition is because the library has been compiled into a dll. Actually, it probably wouldn't be that useful to see the code anyway because it looks like the library is a C# wrapper around native (c or c++) code.
Anyways, I hope the documentation is helpful to you. If you look at page 20 there are some pointers on doing movement. This is going to be a challenge for a new programmer but you can do it. I would suggest you avoid using the event handler to drive your logic and instead stick with using the synchronous versions of commands. Using the synchronous commands your code should operate the same way it reads.
I believe what you'll want to do is add a call to:
Application.DoEvents();
This will allow your application to process posted messages (events), which will allow that global variable to be updated.
I just wanted to know why the MessageBox allowed me to sort of achieve my objectif, but the while loop did not, and how to use it in my advantage here.
The reason that works is because you're giving the WndProc a chance to process events which have been sent to the application. It's not an intended feature of that call to MessageBox.Show();, but it is a consequence. You can do the same thing with a call to Application.DoEvents(); without the interruption of the message box.

Creating Calculator with Class - C#, However unable to solve error

I'm trying to create a Calculator with a Class. However using references from the internet particularly from this website (https://www.sourcecodester.com/tutorials/c/7548/simple-calculator-using-class-c.html)
It did not mention to declare "Information" or whatsoever.
When I typed in the code, the error list return with Information does not exist in current context.
Is there a way to modify the code below? Thank you so much.
public partial class Form4 : Form
{
public Form4()
{
InitializeComponent();
}
private void Form4_Load(object sender, EventArgs e)
{
}
public void RadioButton_Click(object sender, System.EventArgs e)
{
//call a constructor method and return to cal as an instance of a class
calculate cal = new calculate();
//declaring the string variable represent as a textbox
string txtnum1 = TextBox1.Text;
string txtnum2 = TextBox2.Text;
//declaring the double variable
double dbl_val1 = default(double);
double dbl_val2 = default(double);
if (**Information**.IsNumeric(txtnum1) && **Information**.IsNumeric(txtnum2)) //check if the textbox has a numeric value
{
//convert the string to double
dbl_val1 = double.Parse(txtnum1);
dbl_val2 = double.Parse(txtnum2);
//get the value of the converted variable
//to pass it into the variable in the class
cal.num1 = dbl_val1;
cal.num2 = dbl_val2;
//the condition is, if the radiobutton is clicked,
//the operation of MDAS executes.
if (Radio_Multiplication.Checked)
{
//result:
cal.multiply(); //call a subname in a class for multiplying
}
else if (Radio_Addition.Checked)
{
//result:
cal.add(); //call a subname in a class for adding
}
else if (Radio_Subtraction.Checked)
{
//result:
cal.subtract(); //call a subname in a class for subtracting
}
}
else
{
//the result is:
//if the textbox is empty or has a string value
TextBox3.Text = "Enter a number";
return;
}
//put the result of the MDAS to a textbox.
TextBox3.Text = cal.total.ToString();
}
}
I had a quick look at the link and they don't appear to have declared Information anywhere nor have they indicated that they've overridden anything so...I don't know.
That line, however, is just validating that the information entered into the two text boxes are actually numbers and not anything else that can't be calculated.
There are lots of methods you could use to check those numbers. Options would include, but are not limited to:
if(Int32.TryParse(txtNum1, out int temp1) && Int32.TryParse(txtNum2, out int temp2))
{
do stuff;
}
or
if(txtNum1.All(char.IsDigit) && txtNum2.All(char.IsDigit))
{
do stuff;
}
There are other options, but those two might be worth looking into.
Downloading the sample project, I had a look at what Information refers to. Turns out, it's a class from the Microsoft.VisualBasic namespace, presumably for exposing certain aspects of the VB core library to all .NET languages. You can use it in your program by adding a reference to Microsoft.VisualBasic to your project and adding:
using Microsoft.VisualBasic;
to the top of your code file.
(Personally, I can't imagine that this approach is terribly efficient. It's supposed to take an object and determine if it can be evaluated as a number, and I have no idea what approaches it uses to make that deduction based on any random object. You would probably be better off using one of the alternatives that Benny O'Neill suggests.)

Process is terminated due to StackOverflowExeption - C#

I have this problem with my code I cant fix.
I am making a quiz, but the questions mustnt be asked twice so I avoided that, but when it runs out of options it crashes.
It also could be the code after it, but I dont think so.
Here is my code:
private static void chooseQuestion()
{
Random randomQuestion = new Random();
int returnValue = randomQuestion.Next(1, 3);
switch (returnValue)
{
case 1:
if (randomValues.questionOneChosen != 1)
{
questionOne();
}else
{
chooseQuestion();
}
break;
case 2:
if (randomValues.questionTwoChosen != 1)
{
questionTwo();
}else
{
chooseQuestion();
}
break;
}
endQuiz();
}
and here is what is after it:
private static void endQuiz()
{
Console.Clear();
Console.WriteLine();
text.centered("You completed the QUIZ, well done!");
Console.WriteLine();
text.centeredWrite("Press ENTER to go back to the menu");
string input = Console.ReadLine();
if (input == "")
{
Menu.main();
}else
{
endQuiz();
}
}
If you need more code to help me, please ask me.
Thanks in advance!
A StackOverflowException on StackOverflow, nice!
Does Menu.main() end up calling chooseQuestion()?
When a function is called, a new entry on the stack is created with room for all its local variables and the address to return to when it returns. If a function calls itself (recursive call), its original stack entry is not released. If it calls itself often enough without returning, the stack will eventually run out of room and give this exception. Having said that, the stack is pretty big - I think the default is 1 megabyte - so you need a lot of recursive calls to exhaust it.
You've got two functions which both call themselves. If they do it often enough, you'll run out of stack space. Just replace the recursive calls with a loop. Here's your first function:
private static void chooseQuestion()
{
bool endQuizChosen = false;
while ( !endQuizChosen ) {
Random randomQuestion = new Random();
int returnValue = randomQuestion.Next(1, 3);
// ... the rest of the function ...
endQuizChosen = endQuiz();
}
}
and edit endQuiz() to not call itself but to return true if user wants to stop and false if they want to go on.
Your chooseQuestion method's logic goes like this:
a. Pick a 1 or 2, randomly
b. If 1, and I haven't asked question 1 yet, show question 1
c. if 1, and I have asked question 1, go to step (a)
d. repeat (b) and (c) for question 2
...
once you answer both questions, you try to get another random question. But there are no more questions available. So you recurse into an exception.
Don't write your logic this way. Use a collection, and remove the item from the collection after you've shown that question. When the collection is empty, you're done.
This is never getting set.
randomValues.questionOneChosen
So whatever it is, if it's not 1 or 2 the method is going to call itself again. It's probably always zero.

Strange "Collection was modified after the enumerator was instantiated" exception

Perhaps someone can point me in the correct direction, because I'm completely stumped on this.
I have a function that simply prints out a LinkedList of classes:
LinkedList<Component> components = new LinkedList<Component>();
...
private void PrintComponentList()
{
Console.WriteLine("---Component List: " + components.Count + " entries---");
foreach (Component c in components)
{
Console.WriteLine(c);
}
Console.WriteLine("------");
}
The Component object actually has a custom ToString() call as such:
int Id;
...
public override String ToString()
{
return GetType() + ": " + Id;
}
This function typically works fine - however I've run into the issue that when it builds to about 30 or so entries in the list, the PrintcomplentList foreach statement comes back with an InvalidOperationException: Collection was modified after the enumerator was instantiated.
Now as you can see I'm not modifying the code within the for loop, and I haven't explicitly created any threads, although this is within an XNA environment (if it matters). It should be noted that the printout is frequent enough that the Console output is slowing down the program as a whole.
I'm completely stumped, has anyone else out there run into this?
I suspect the place to start looking will be at any places where you manipulate the list - i.e. insert/remove/re-assign items. My suspicion is that there will be a callback/even-handler somewhere that is getting fired asynchronously (perhaps as part of the XNA paint etc loops), and which is editing the list - essentially causing this problem as a race condition.
To check if this is the case, put some debug/trace output around the places that manipulate the list, and see if it ever (and in particular, just before the exception) runs the manipulation code at the same time as your console output:
private void SomeCallback()
{
Console.WriteLine("---Adding foo"); // temp investigation code; remove
components.AddLast(foo);
Console.WriteLine("---Added foo"); // temp investigation code; remove
}
Unfortunately, such things are often a pain to debug, as changing the code to investigate it often changes the problem (a Heisenbug).
One answer would be to synchronize access; i.e. in all the places that edit the list, use a lock around the complete operation:
LinkedList<Component> components = new LinkedList<Component>();
readonly object syncLock = new object();
...
private void PrintComponentList()
{
lock(syncLock)
{ // take lock before first use (.Count), covering the foreach
Console.WriteLine("---Component List: " + components.Count
+ " entries---");
foreach (Component c in components)
{
Console.WriteLine(c);
}
Console.WriteLine("------");
} // release lock
}
and in your callback (or whatever)
private void SomeCallback()
{
lock(syncLock)
{
components.AddLast(foo);
}
}
In particular, a "complete operation" might include:
check the count and foreach/for
check for existance and insert/remove
etc
(i.e. not the individual/discrete operations - but units of work)
Instead of foreach, I use while( collection.count >0) then use collection[i].
I don't know if this is relevant to the OP but I had the same error and found this thread during a google search. I was able to solve it by adding a break after removing an element in the loop.
foreach( Weapon activeWeapon in activeWeapons ){
if (activeWeapon.position.Z < activeWeapon.range)
{
activeWeapons.Remove(activeWeapon);
break; // Fixes error
}
else
{
activeWeapon.position += activeWeapon.velocity;
}
}
}
If you leave out the break, you will get the error "InvalidOperationException: Collection was modified after the enumerator was instantiated."
Using Break could be a way but it may impact your series of operation.
What I do in that case in simply convert the foreach to traditional for loop
for(i=0; i < List.count; i++)
{
List.Remove();
i--;
}
This works without any issues.

Categories