alternative to global variable for running total - c#

I am very much a novice with C#, but for class I must write a program which keeps a running total of food sales.
We have been taught to use global variables, but not only does it not feel like the easiest way, everywhere I read says not to use them.
Without using a global variable, I have not found a way to keep the cost of two different chocolate bars, for example. It works to get the total cost of 1 bar - regardless of quantity, but when I add another, any previous selections disappear from the total cost.
So in short: what I have works, but I feel that I need to learn a better practice.
Here is a sample of my code (note that there is more than one method which I have not copied into here):
class Program
{
static double total = 0;
static void Main(string[] args)
{
string choice = "y";
while (choice == "y")
{
Console.WriteLine("Main Menu:\nChocolate\nSandwiches");
int menu = int.Parse(Console.ReadLine());
switch (menu)
{
case 1:
chocolate();
break;
case 2:
sandwich();
break;
}
}
}
static void chocolate()
{
int menu = 0;
//double cost = quant * price;
Console.WriteLine("Chocolate bar menu:\nMars bar\nSnickers\nTwix\nMilky Bar\nTurkish Delight");
int chocBar = int.Parse(Console.ReadLine());
Console.Clear();
Console.WriteLine("Quantity");
double quant = double.Parse(Console.ReadLine());
Console.Clear();
if (chocBar == 1)
{
Console.Clear();
double costMars = quant * 0.5;
total = total + costMars;
Console.WriteLine("current total: £" + total.ToString("0.00"));
Console.WriteLine("Press 1 for main menu");
menu = int.Parse(Console.ReadLine());
if (menu == 1)
{
chocolate();
}
else
{
}
}
if (chocBar == 2)
{
double costSnick = quant * 0.8;
total = total + costSnick;
Console.WriteLine("current total: £" + total.ToString("0.00"));
Console.WriteLine("Press 1 for main menu");
menu = int.Parse(Console.ReadLine());
if (menu == 1)
{
chocolate();
}
else
{
}
}
Console.ReadKey();

Congratulations for picking C# as a programming language to learn. It really is a great programming language.
What you read is correct, globals variables have many implications. It is usually last resort. Since you are a beginner I will recommend another simple technique. I found a great link for you that will introduce concepts of passing variables by value and reference. Check it out
https://msdn.microsoft.com/en-us/library/0f66670z.aspx
Good luck.

Good on you for being proactive about learning good coding practices early on in your education! I think your instructor is probably telling you to use globals for right now so as not to overload you with too many concepts at once, but yeah, as a general rule of thumb, you should try to pass variables around your code as method parameters instead. The reason is because if you have a global variable in a large application with tens of thousands of lines of code, and that global variable somehow ends up with an incorrect value in it, then it can be a real nightmare to figure out which piece of code wrote the incorrect value into the variable.

There are a variety of ways to allow data to flow into and out of methods when calling them. The worst way is to use a "global" variable.
Aside: C# technically does not have "global" variables per se, as the term describes variables that are fully accessible from any context, without qualification. All C# variables must be scoped to a type or a method. But, the same general caveats do apply to these kinds of variables. In C#, it is the usage that distinguishes whether a variable is declared along best practices or not, not the declaration itself.
In your code, the most obvious way to approach this would be to have each method return the value to be added to the total.
Also, for some reason, you have implemented your loop as a recursive call. It's not clear to me why you did that, but unless that's a specific requirement in your classroom assignment, I would recommend against it. You're unlikely to run into any specific problems in this context, but it's highly irregular, making the code hard to understand, and giving at least a theoretical possibility of a stack overflow (in reality, you're unlikely to find a user patient enough to cause that to happen, but there's not an actual guarantee in the code that the recursion will eventually terminate).
I am usually hesitant to rewrite homework assignments, but in this case I think you've provided enough code, and advice will be clearer if presented as more code. You still have some work to do, to implement the sandwich() method and the other candy bars, and I've only addressed the immediate problem, not the other flaws in the code. So I don't feel like I'm just doing your school-work for you. :)
Here is the general idea of what I think the code should look like:
class Program
{
static void Main(string[] args)
{
double total = 0;
string choice = "y";
while (choice == "y")
{
Console.WriteLine("Main Menu:\nChocolate\nSandwiches");
int menu = int.Parse(Console.ReadLine());
switch (menu)
{
case 1:
total += chocolate();
break;
case 2:
total += sandwich();
break;
}
Console.WriteLine("current total: £" + total.ToString("0.00"));
Console.WriteLine("Press 1 for main menu");
menu = int.Parse(Console.ReadLine());
}
}
static double chocolate()
{
int menu = 0;
Console.WriteLine("Chocolate bar menu:\nMars bar\nSnickers\nTwix\nMilky Bar\nTurkish Delight");
int chocBar = int.Parse(Console.ReadLine());
Console.Clear();
Console.WriteLine("Quantity");
double quant = double.Parse(Console.ReadLine());
Console.Clear();
double cost;
switch (chocBar)
{
case 1: // Mars
cost = 0.5;
break;
case 2: // Snickers
cost = 0.8;
break;
default:
throw new Exception("Invalid user input");
}
Console.Clear();
return quant * cost;
}
}
Notes:
It's not clear to me what the point of the prompt for "Press 1 for main menu" is. What could the user do at that point except return to the main menu? Whatever your intent, it seems likely that you will do the same thing regardless of whether the user has picked the "Chocolate" or "Sandwiches" option, so I moved that prompt out to the main method.
When dealing with currency, the decimal type is a better choice than double, because human beings don't like the rounding error that occurs with base-2 arithmetic in the double type. Using the decimal type ensures that values entered as decimal remain represented exactly as decimal, eliminating that source of rounding error.
It's not clear to me whether you really intend for the user to be able to purchase a fraction of a candy bar or not. The type you parse the input as is double, which would allow the user to buy e.g. 1.25 candy bars. If that's what you intend, fine. But if not, consider changing the type of the quantity user input to int.
Your original code had no mechanism to break out of the top-level menu, i.e. set choice to something other than "y". I did nothing at all to address that. The same issue still exists in the code here.
"Turkish Delight" may describe a wide variety of candies, but I've never seen one that I'd characterize as a chocolate bar. You might want to reconsider the top-level menu item description, if you're going to sell those. :)

Related

How to reference a variable outside of scope in C#

My professor asked us to create a program that takes in a user's height and weight, and then calculate bmi. I decided to take it a little further and added in some "input validation" logic. This means that if someone inputs "cat" for their weight, it let the user know that "cat" is not a valid weight.
class MainClass
{
public static void Main ()
{
float userWeight;
float userHeight;
bool weight = true;
Console.Write ("Weight: ");
while (weight)
{
var inputWeight = (Console.ReadLine ());
if (!float.TryParse (inputWeight, out userWeight)) {
Console.WriteLine ("Invalid input");
Console.Write ("Please try again: ");
}
else
{
weight = false;
}
}
bool height = true;
Console.Write ("Height: ");
while (height)
{
var inputHeight = (Console.ReadLine ());
if (!float.TryParse (inputHeight, out userHeight)) {
Console.WriteLine ("Invalid input");
Console.Write ("Please try again: ");
}
else
{
height = false;
}
}
float bmiHeight = userHeight * userHeight; // error for userHeight
float bmi = userWeight / bmiHeight * 703; // error for userWeight
Console.WriteLine ("You BMI is " + bmi);
}
}
The error I get is "use of unassigned local variable..". I know that I am assigning the user variables within IF statements, and that they only persist until the end of that IF statement.
My question is, how do I assign a variable in an if statement, and then reference the new value of that variable outside of that statement?
Certainly I don't have to nest them all, because that seems tedious....
The issue here is that there is a chance that your variables userHeight and userWeight are still holding garbage value since you did not initialize them.
You can try initializing them with a default valid:
float userHeight = DEFAULT_HEIGHT;
float userWeight = DEFAULT_WEIGHT;
Do ... while(condition) is more suitable for your case and also allows compiler to confirm that value is actually assigned:
var isHeightValid = false;
do
{
var inputHeight = (Console.ReadLine ());
if (!float.TryParse (inputHeight, out userHeight)) {
Console.WriteLine ("Invalid input");
Console.Write ("Please try again: ");
}
else
{
isHeightValid = false;
}
}
while (!isHeightValid);
Why: compiler is not smart enough to figure out that first iteration of while(condition) for general case will be always executed, so it assumes that code inside while may not run and hence variables will not be assigner. Yes, in your particular case it actually possible to detect that first iteration runs, but likely this is not common enough case to add rule to compiler.
do ... while on other hand guarantees that at least one iteration happens and hence variable assignment (via out parameter) will always happen from compiler's point of view.
public void heightAndWeight()
{
double height = getValue("What is your height in inches?",36,80);
double weight = getValue("What is your weight in kilograms?",45,135);
if (height > 0 && weight > 0)
{
Console.WriteLine("your BMI is " + (height * weight).ToString("N2"));
}
}
private double getValue(string question,int lowRange,int highRange) {
double ret = 0;
while(ret==0){
Console.WriteLine(question);
string retStr = Console.ReadLine();
if(double.TryParse(retStr,out ret))
{
if(ret<lowRange||ret>highRange){
Console.WriteLine("You must enter a value between "+lowRange.ToString()+" and "+highRange.ToString()+". Please try again.");
ret=0;
}else{
return ret;
}
}else{
Console.WriteLine("Invalid entry. Please try again");
}
}
return ret;
}
Why don't you want to initialize the variables with some, for example, negative value; Your code is not going to reach the end until valid values are inserted by user anyway
Fulfilling your request would defeat the purpose of local scope. It cannot be done for good reason. The point of declaring a variable locally is to not litter the broader scope with noise. In your case it isn't noise, your userWidth and userHeight variables have meaning in the main scope because you use them there. So you either initialize them properly up in the method or you declare them inside the if and move the code that uses them into the if section as well. The latter would mean you have some double code, you can fix that by moving the calculation of the BMI to a separate method and call that from within both if sections, passing your variables as arguments and getting back the BMI.
It would not be nice to put the calculation and the output statement in the same method. But that's another story.

alternative FizzBuzz example error

Been trying to solve this for 2 days now and I just can't get it to work! The programs layout has to stay the same (part of the challenge). Really bugging me and hoping somebody could shed some light...
I keep getting the following error:
Use of unassigned local variable 'countOfFizz'
Use of unassigned local variable 'countOfBuzz'
Use of unassigned local variable 'countOfFizzBuzz'
Use of unassigned local variable 'countOfPrime'
On these lines:
fb.IsFizz(input, countOfFizz);
fb.IsFizz(input, countOfBuzz);
fb.IsFizz(input, countOfFizzBuzz);
fb.IsFizz(input, countOfPrime);
and here is the full code. (again apologies if its poor coding, its basics and the layout has been supplied already).
class FizzBuzz
{
public static void Main()
{
int input;
string enter;
int countOfFizz;
int countOfBuzz;
int countOfFizzBuzz;
int countOfPrime;
Console.WriteLine("Please enter a number: ");
enter = Console.ReadLine();
input = int.Parse(enter);
while (input != 0)
{
Console.WriteLine("Please enter a number: ");
enter = Console.ReadLine();
input = int.Parse(enter);
FizzBuzz fb = new FizzBuzz();
fb.IsFizz(input, countOfFizz);
FizzBuzz fb1 = new FizzBuzz();
fb1.IsBuzz(input, countOfBuzz);
FizzBuzz fb2 = new FizzBuzz();
fb2.IsFizzBuzz(input, countOfFizzBuzz);
FizzBuzz fb3 = new FizzBuzz();
fb3.IsPrime(input, countOfPrime);
FizzBuzz fb4 = new FizzBuzz();
fb4.TotalFizz(countOfFizz);
FizzBuzz fb5 = new FizzBuzz();
fb5.TotalBuzz(countOfBuzz);
FizzBuzz fb6 = new FizzBuzz();
fb6.TotalFizzBuzz(countOfFizzBuzz);
FizzBuzz fb7 = new FizzBuzz();
fb7.TotalPrime(countOfPrime);
}
Console.WriteLine("Finished.");
}
public bool IsFizz(int input, int countOfFizz)
{
if (input % 9 == 0)
{
Console.WriteLine("Fizz");
countOfFizz++;
return true;
}
return false;
}
public bool IsBuzz(int input, int countOfBuzz)
{
if (input % 13 == 0)
{
Console.WriteLine("Buzz");
countOfBuzz++;
return true;
}
return false;
}
public bool IsFizzBuzz(int input, int countOfFizzBuzz)
{
if (input % 9 == 0 && input % 13 == 0)
{
Console.WriteLine("FizzBuzz");
countOfFizzBuzz++;
return true;
}
return false;
}
public bool IsPrime(int input, int countOfPrime)
{
for (int i = 2; i < input; i++)
{
if (input % i == 0 && i != input)
{
return false;
}
}
Console.WriteLine("Prime");
countOfPrime++;
return true;
}
public void BeginTesting(int countOfFizz, int countOfBuzz, int countOfFizzBuzz, int countOfPrime)
{
countOfFizz = 0;
countOfBuzz = 0;
countOfFizzBuzz = 0;
countOfPrime = 0;
}
public int TotalFizz(int countOfFizz)
{
Console.WriteLine("Number of Fizz: ");
return countOfFizz;
}
public int TotalBuzz(int countOfBuzz)
{
Console.WriteLine("Number of Buzz: ");
return countOfBuzz;
}
public int TotalFizzBuzz(int countOfFizzBuzz)
{
Console.WriteLine("Number of FizzBuzz: ");
return countOfFizzBuzz;
}
public int TotalPrime(int countOfPrime)
{
Console.WriteLine("Number of Prime: ");
return countOfPrime;
}
}
The problem is that you are passing ints to the methods, when an int (or float, bool, etc.) is passed to a method it is copied, it is not passed as a reference variable. Therefore, the countOfBuzz you change within a method is not the same as the one in the main method.
To solve this, don't pass those parameters to the methods. Instead, change the scope of those variables to be inside the class instead of inside the main method.
Also, it is good practice to initialize the variables to zero (local variables within methods need to be initialized, otherwise you get that message you asked about).
As Simon already explained in his answer, integers are value types and all value types are always passed by value (by default). This means that when you call for example IsFizz with the countOfFizz then all that happens is that the value of that variable is passed to the function which then has its own variable with a copy of the value. So when the function changes the value, then only that local variable’s value changed but that change will never make it to the original variable.
One way to solve this would be to explicitely pass those variables by reference. You can do this by using ref int countOfFizz in the function signature for the parameter (i.e. add the ref keyword). However, I do not recommend you to do this as it will not provide a state the FizzBuzz class was likely to have.
So, in object oriented programming, you create objects which hold a state. In your case, the FizzBuzz is the class, the type of those objects. Now if we think about it, and take into account that you apparently want to keep count of the number of Fizz/Buzz/FizzBuzz cases, it makes sense to have those counts contained within the object.
So first of all, you should make those countOfX variables instance variables that are bound to the object.
Looking at the IsFizz etc. methods, they are all supposed to return a boolean value. So it’s likely that they were originally only meant to check the input and return true or false depending on if the check succeeded or not. Here we can also increment our counters when we find a falue. So in the end, those methods should only take the input, perform the check, increment their counter and return the check result.
The TotalX methods can then simply return the current counter results, while the BeginTesting method can reset them to zero (without taking any parameters).
Finally, in the Main function you want to create only a single instance of FizzBuzz so we can share the state during the whole duration of the program. You should check the return values for the IsX methods and print the appropriate response here (often you don’t want class types to arbitrarily print stuff but handle that in a different layer—in your case the console application that happens in the Main function).
As a final note, I would like you to know that I’m interpreting a lot into the original task here and cannot perfectly say what the original intention behind this code was. From my point of view it looks a bit ridiculous to do it like that. The FizzBuzz problem, even in this changed instance, is a simple problem aimed to show if a person is capable of basic programming-related thinking. It’s not necessarily meant to be a problem to work on in a complex object oriented manner, but just like the typical “Hello World” there seem to be people who like over-generalizing it in a way to make it terribly complex for fun or practice. I’m not really agreeing that this FizzBuzz instance with that predefined base code is either generalized nor fun nor a good practice. But again, this is just my opinion.
And finally a last hint to complete this “correctly”: The output “FizzBuzz” is the combination of both conditionals for “Fizz” and ”Buzz”. Meaning that if a number qualifies for “FizzBuzz” it also does so for the individual ones. So you should make sure that the check for the individual ones either explicitely prevent the “FizzBuzz” combination to match, or you check for the combined one first and abort further checks if it matches.
Initialize the variables might do the trick for you:
int countOfFizz = 0;
int countOfBuzz = 0;
int countOfFizzBuzz = 0;
int countOfPrime = 0;
Remove the parameter (countOfFizz) from IsFizz, and it should work. Do the same for other similar methods.
public bool IsFizz(int input)
{
if (input % 9 == 0)
{
Console.WriteLine("Fizz");
countOfFizz++;
return true;
}
return false;
}

Making a program do absolutely nothing when certain data or wrong answer is put in

I have this crazy idea, I would like a program to not execute anything if the wrong data is put into the console. Such as the alphabet, weird characters. All I want is decimal numbers and a period to be accepted. If the wrong data is typed in, I want the program to STAY there and do absolutely nothing after you hit enter.
My mindset thinks:
if (sum != decimal)
{
// Don't do anything, just leave it as is.
code I have no clue about.
}
Now, you must be thinking, you can't use datatypes for an if statement! Maybe you can, but its not working for me. I'm sorry I'm a big noob.
try
{
Console.WriteLine("Put in the price of the product");
string input = Console.ReadLine();
decimal sum = Convert.ToDecimal(input);
if (sum <= 100)
{
decimal totalprice = sum * .90m;
Console.WriteLine("Your final price is {0:0:00}", totalprice);
}
}
catch
{
}
I was also thinking maybe a try and catch statement would work too, but again, I have no clue what to put in that either.
If your answers could be noob-safe and explained. (Because I want to learn the concept of how this stuff works) that would be nice.
A visual example:
When you hit enter, nothing happens but when you put in the correct datatype, the program will continue.
Datatypes are not written to console. Only strings could be retrieved from console input. What type has string "2" - decimal, int, byte, string? All you can do is try to parse some type from your input string:
Int32.TryParse("2", out value)
For your case:
Console.WriteLine("Put in the price of the product");
string input = Console.ReadLine();
decimal sum;
if (!Decimal.TryParse(input, out sum))
{
Console.WriteLine("Decimal number cannot be parsed from your input.");
return;
}
if (sum <= 100)
Console.WriteLine("Your final price is {0:0:00}", sum * 0.90M);
UPDATE
Decimal.TryParse - Converts the string representation of a number to its Decimal equivalent. A return value indicates whether the conversion succeeded or failed. It does not throws an exception if conversion failed.
! Operator - it is NOT operator. The logical negation operator (!) is a unary operator that negates its operand. It is defined for bool and returns true if and only if its operand is false.
So if (!Decimal.TryParse(input, out sum)) verifies if conversion was NOT successful. Then I put a sample message for user and exited from method (if it was your Main method, then program will terminate. But this all is out of your initial question about parsing strings.
Try this (note the while/break pairing):
while (true)
{
string input = Console.ReadLine();
decimal sum;
if (Decimal.TryParse(input, out sum) == true)
{
if (sum <= 100)
{
decimal totalprice = sum * .90m;
Console.WriteLine("Your final price is {0:0:00}", totalprice);
break; // break out of while
}
}
}
The conversion function you are using will I believe throw an exception if it cannot convert the string passed to the requested type. Generally, exceptions should be avoided for controlling program flow, and reserved for truly unexpected situations. Instead, you should look to using a method that doesn't throw an exception, but instead returns a value to indicate success or failure. With this in ind you could try:
try
{
Console.WriteLine("Put in the price of the product");
decimal sum;
// Repeat forever (we'll break the loop once the user enters acceptable data)
while (true)
{
string input = Console.ReadLine();
// Try to parse the input, if it succeeds, TryParse returns true, and we'll exit the loop to process the data.
// Otherwise we'll loop to fetch another line of input and try again
if (decimal.TryParse(input, out sum)) break;
}
if (sum <= 100)
{
decimal totalprice = sum * .90m;
Console.WriteLine("Your final price is {0:0:00}", totalprice);
}
}
catch
{
}

C# variable scopes and the "switch" statement?

This kind of code would normally work in PHP, but since the scope is much more strict in C#, it's not. I can't figure out a way to write this code without repeating myself.
static double Cube()
{
Console.Write("Enter the side length of the cube: ");
try
{
double x = Convert.ToDouble(Console.Read());
return Math.Pow(x, 3);
}
catch (FormatException)
{
Console.WriteLine("Invalid input, please enter a number.");
Cube();
}
return 1;
}
..Later in Main():
switch (choice)
{
case 0:
return;
case 1:
double final = Cube();
break;
default:
Console.WriteLine("Please enter 0 or 1.");
Main();
break;
}
Console.WriteLine("The volume is: {0}", Convert.ToString(final));
The Cube() method works fine, but it's messy in my opinion (return 1 at the end to make the compiler happy). But an error comes up saying The name 'final' does not exist in the current context. It can't find final. So the only way to make this work that I'm seeing is to put the Console.WriteLine statement right after the double final = Cube().
I've also tried declaring double final; outside the switch, then just setting final inside each case, but that hasn't worked either.
Thanks!
You're right: this is a mess. Start over.
Your fundamental problem is that you're not separating your concerns. You have a method that does user input, input validation, retry logic and math all at the same time. You should rather make methods for each.
Also, use TryParse to handle the failure case, not exception handling.
Finally, recursion is completely the wrong tool to use. A problem must have the following characteristics to be solved by recursion:
A trivial base case.
Can be reduced to a set of smaller problems.
Solutions to smaller problems can be combined to solve larger problems.
Making a problem smaller repeatedly eventually gets to the trivial case.
Your problem has none of these properties, so recursion is automatically the wrong tool. The tool you want is a loop.
static void Main()
{
double x = ObtainDoubleFromUser(
"Enter the side length of the cube: ",
"Please enter a number: ");
Console.WriteLine("The volume is {0}", Cube(x));
}
static double ObtainDoubleFromUser(string firstMessage, string failureMessage)
{
Console.Write(firstMessage);
while(true)
{
double result;
if (Double.TryParse(Console.Read(), out result))
return result;
Console.Write(failureMessage);
}
}
static double Cube(double x)
{
return Math.Pow(x, 3);
}
Does that all make sense? You want to avoid recursion and exception handling if you possibly can. And keep your concerns separated.
If you want to access final from outside the switch scope, you'll have to declare it outside that scope too. If you reference final and there are code paths that allow not setting a value to final, then the compiler will be "angry".
In php, final would magically be 0 when you don't assign anything to it. Try declaring final before the switch, and then assign a value to it at each case statement including the default case.
Place the variable declaration before your switch statement:
double final = 0.0;
switch(choice)
{
...
}
Then just use the variable in your switch statement:
case 1:
final = Cube();
break;
In C#, variables must be declared before they can be used. In your code, the declaration was limited to the scope of the switch statement. Declaring the variable prior to the switch statement ensures that its in the scope of the method, allowing it to be used inside and after the switch statement.

using switch statements with constants or enumerations? (Which is better)? C#

HI, I've got a simple question, but one that has been bugging me for a while.
Question:
When using switch statements in C#, is it considered better practice to use enums over constants or vice versa? Or is it a matter of preference? I ask this because many people seem to like using enums, but when you are switching on an int value, you have to cast each of the values contained in the enum to an int, even if you specify the type of enum.
Code Snippet:
class Program
{
enum UserChoices
{
MenuChoiceOne = 1,
MenuChoiceTwo,
MenuChoiceThree,
MenuChoiceFour,
MenuChoiceFive
}
static void Main()
{
Console.Write("Enter your choice: ");
int someNum = int.Parse(Console.ReadLine());
switch (someNum)
{
case (int)UserChoices.MenuChoiceOne:
Console.WriteLine("You picked the first choice!");
break;
// etc. etc.
}
}
}
Is there some way you can create an instance of the enum and just cast the whole enum to an int?
Thanks!
Why not do this instead?
UserChoices choice = (UserChoices)int.Parse(Console.ReadLine());
switch (choice)
{
case UserChoices.MenuChoiceOne:
// etc...
Then you only need to cast once.
Update: fixed bug in code!
I think the preference of enums over constants is because of readability and not because of performance. I find it easier to read enums in code (in general and not just in switch statements), than to read/understand constants and their usage.
and btw, you don't have to cast every case, you can just cast your switch.
switch((UserChoices)someEnum)
{
...
I believe you can simply do:
switch((UserChoices)someNum)
{
case UserChoices.MenuChoiceOne:
break;
default:
throw Exception // whatever here
}

Categories