0

Sorry if this is a simple question; this is my first language and I'm trying my best to seek out and follow examples and explanations on this site and otherwise.

I've been trying to expand on a Microsoft C# tutorial program that creates "bank accounts." I'm trying to work on catching and handling exceptions, specifically by prompting the user to try again for a valid input.

I've come across this thread and many similar threads about running a loop while the input is invalid, and this example specifically using try/catch, which if I'm understanding correctly, is what I want to use here because I have a few lines of code that could throw multiple exceptions (it could be non-numerical or it could be negative). Following those and other examples, I can't figure out how to assign the initial balance input to a value that I can reference outside the loop (but still only within the CreateAccount method) once the input is valid.

I'm not sure what I have currently is working otherwise, but currently this code produces an error because initBalInput is left unassigned after the while loop, even though it's declared outside the loop and assigned in the try block.

public static void CreateAccount()
        {


            // Prompt for BankAccount constructor parameter {name} which is passed to BankAccount.Owner in constructor
            Console.WriteLine("Name on new account: ");
            string nameInput = Console.ReadLine();

            decimal initBalInput;
            bool valid = false;
            while (valid == false)
            {
                try
                {
                    Console.WriteLine("How much to deposit for initial balance: ");
                    initBalInput = Convert.ToDecimal(Console.ReadLine());
                }
                catch (ArgumentOutOfRangeException)
                {
                    Console.WriteLine("Initial balance must be positive!");
                    valid = false;
                    continue;
                }
                catch (FormatException)
                {
                    Console.WriteLine("Initial balance must be a number!");
                    valid = false;
                    continue;
                }
                valid = true;
            }

            // Create new instance "account" of type BankAccount and set its parameters
            BankAccount account = new BankAccount(nameInput, initBalInput);
            Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance.");
        }
5
  • 1
    Set a default value when you define your variable like so decimal initBalInput = 0; and your code should compile. When I write code I use decimal.TryParse(inStringValue, out parsedDecimal) instead of catching exceptions. learn.microsoft.com/en-us/dotnet/api/… If your code can avoid exceptions it is almost always better to do so. Commented Jun 8, 2020 at 14:09
  • 2
    Tip: Avoid using the Convert class entirely. Always prefer the static .TryParse methods instead. Commented Jun 8, 2020 at 14:10
  • 1
    Additional hint: Do not catch exceptions and ignore the exception message/details. Commented Jun 8, 2020 at 14:11
  • @mortb TryParse has been recommended, but then how do I also check that the input is a positive value, if not catching an exception for the invalid values that TryParse doesn't check for? Commented Jun 8, 2020 at 14:33
  • @Kacey The same way you'd check for a positive number normally with value > 0. TryParse has the exact same parsing rules as Parse and Convert.To..., the only difference is it doesn't throw and it positively identifies parsing errors (as the Convert class does not distinguish between default values and error conditions, unfortunately. Commented Jun 9, 2020 at 0:40

3 Answers 3

1

Instead of catching the exceptions, write the code that handles the invalid input.

public static void CreateAccount()
        {


            // Prompt for BankAccount constructor parameter {name} which is passed to BankAccount.Owner in constructor
            Console.WriteLine("Name on new account: ");
            string nameInput = Console.ReadLine();

            string initBalInput = Console.ReadLine();
            // try parse will check for invalid decimal values and also, positive values can be checked
            if(decimal.TryParse(initBalInput, out decimal initBal) && initBal > 0) {

                // Create new instance "account" of type BankAccount and set its parameters
                BankAccount account = new BankAccount(nameInput, initBal);
                Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance.");


            } else {
             Console.WriteLine("Invalid initial balance");
          }
        }
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the reply. A couple questions - These changes allow the program to run like it was before I started working on exception handling, but my goal here is to stop the program from exiting when an invalid input is given, and instead tell the user why the input was invalid and re-prompt them. This code seems to function the same as mine did before I wrapped it in try/catch - it exits with an exception if an invalid character or negative number is given. How do I accomplish what I'm looking for with this way of doing it?
0

but currently this code produces an error because initBalInput is left unassigned after the while loop, even though it's declared outside the loop and assigned in the try block

The problem is that the compiler doesn't know if execution will ever reach the try block:

while (valid == false)

is evaluated at runtime. You and me both know that execution will enter at least once the while loop because valid is initially false but the compiler doesn't go into that type of analysis where variables are involved and therefore assumes execution might never enter the while loop and an unitialized initBalInput can be read.

That said, you should not get into the habit of using exepctions as control flow mechanisms. Exceptions should be exceptions, don't base the logic of your programs around exceptions. In your case, you should look into the method decimal.TryParse.

Also, always break up your problem into smaller problems. At the beginning, start small, make one liner methods that are obviously correct. It's very hard to write a bug in methods that are one or two lines long.

So what do you need?

  1. A method that prompts the user for an input.
  2. A method that validates the input
  3. Something that asks the user to try again if the input is wrong.

Ok, numer one:

static string RequestUserInput(string message)
{
    Console.Write(message);
    return Console.ReadLine();
}

Number two: We already have it with decimal.TryParse(string, out decimal d). This method will return true if the input string can be parsed into a valid decimal number which will be assigned to d and false otherwise.

Number three:

public static decimal GetDecimalInput(string message)
{
    decimal d;

    while (true)
    {
        if (!decimal.TryParse(RequestUserInput(message), out d))
            //tryparse failed, input is not a valid decimal number
            Console.WriteLine("Initial balance must be a number!");
        else if (d < 0) //try parse succeeded, we know input is a valid
                        // decimal number d but it might be negative.
            Console.WriteLine("Initial balance must be positive!");
        else
            //we know inout is a valid non negative decimal number.
            //break out of the loop, we don't need to ask again.
            break;
    }

    return d;
}

And now, you put it all together:

var accountBalance = GetDecimalInput("How much to deposit for initial balance: ");

5 Comments

Someone else said I should replace catching exceptions with the TryParse method, but it still exits the program if the check fails, and I'm left without a way to check for a positive value. If not exceptions, what do I use to accomplish what I'm trying to do? The program was reading an referencing the inputs just fine before, but I'm hoping to handle an input of either a letter or negative number by displaying why the input was invalid and prompting the user for input again.
@KaceyO'Harra See edits to answer. Break down the problem into helper methods, it will be much easier.
Thank you for such a comprehensive answer. I get two errors when trying to build this to your example. One says public is invalid for GetDecimalInput, but removing the public keyword doesn't seem to break anything. For the other, what goes between (!decimal.TryParse and (message, out d) ? RequestUser has no definition, but RequestUserInput says "no overload for method RequestUserInput takes 2 arguments"
Also just checked removing that part altogether and leaving that line as "if (!decimal.TryParse(message, out d))" and the program runs an infinite loop of "Initial balance must be a number" after the name is entered. I might be able to work with this, was that the right fix?
@kacey oops sorry missing a ) after message.
0

First, I have two articles on Exception handling that I consider required reading:

  1. This one helps to classify the 4 common exception types - and if you should even consider catching them.
  2. While this one goes into more details for good practices.

You should not be using convert, but parse. Or even better TryParse(). The exceptions on the string -> number conversion are the examples for vexing exceptions.

If there is no TryParse, I did once wrote a custom implementation of Int.TryParse() for someone still on Framework 1.1:

//Parse throws ArgumentNull, Format and Overflow Exceptions.
//And they only have Exception as base class in common, but identical handling code (output = 0 and return false).

bool TryParse(string input, out int output){
  try{
    output = int.Parse(input);
  }
  catch (Exception ex){
    if(ex is ArgumentNullException ||
      ex is FormatException ||
      ex is OverflowException){
      //these are the exceptions I am looking for. I will do my thing.
      output = 0;
      return false;
    }
    else{
      //Not the exceptions I expect. Best to just let them go on their way.
      throw;
    }
  }

  //I am pretty sure the Exception replaces the return value in exception case. 
  //So this one will only be returned without any Exceptions, expected or unexpected
  return true;
}

But that code looks like you want to have detailed information why it failed. At wich point you may have to write a detailed list of catch blocks.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.