kcoha
kcoha

Reputation: 3

Implementing a while loop to check for multiple exceptions in input validity

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.");
        }

Upvotes: 0

Views: 471

Answers (3)

InBetween
InBetween

Reputation: 32740

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: ");

Upvotes: 0

Sunny
Sunny

Reputation: 4809

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");
          }
        }

Upvotes: 1

Christopher
Christopher

Reputation: 9804

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.

Upvotes: 0

Related Questions