Working our way out of this mess
You have been given some good tips about separating concerns and coding to prevent exceptions. The application itself should be a very simple loop. Parsing user input and formatting output takes up most of the code.
Ideally, we would like to end up with this loop:
public static void Run()
{
UserPane.AppInfo();
while (true) // until 'EXIT' is typed by user
{
UserPane.PersonInfo(UserPane.ReadPerson());
}
}
To get there, we require some refactoring. Also note, that unlike your perpetual checks for early exit, I would opt to let a low level handler (shown later on) exit the process on one place (DRY). This enables us to avoid redundant code in the likes of..
if (person.fname.ToLower() == "exit") break;
Let's start with cleaning up Person
. As suggested by others, you should adhere to Separation of Concerrns. We will have to take out the Console
printing. Furthermore, we need proper naming and member declaration conventions. Do not abbreviate member names. Prefer properties over public fields.
public string fname;
public string mname;
public string lname;
public int age;
public string phnumber;
We end up with this POCO:
class Person
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string PhoneNumber { get; set; }
}
It's time to focus on user input. You already started a class KeyInput
, which is a good idea to offset Console
interop to. But you could have gone much further with this design to allow for clean application code.
internal class KeyInput
{
public static string keyIn(string scrtext)
{
// .. code omitted for brevity
}
}
This should be avoided in application code:
while (true)
{
try
{
age = Convert.ToInt32(KeyInput.keyIn("\nPlease enter your age"));
break;
}
catch (ArgumentException)
{
Console.WriteLine("No value was entered");
}
// .. code omitted for brevity
}
Let's make a class UserPane
, which will handle both user input and formatted output. The code method for reading input is Read<T>
. It provides a generic way to prompt the user for input, validate the input, allowing for retries, exiting the application on demand and projecting input to any other meaningful type.
static class UserPane
{
const string Exit = "exit";
static T Read<T>(string prompt, Func<string, bool> validator, Func<string, T> projector,
string validationMessage = null, Action exitHandler = null)
{
string input = default;
var validated = false;
Console.WriteLine($"\r\n{prompt}");
do
{
input = Console.ReadLine().Trim();
if (input.ToLowerInvariant().Trim().Equals(Exit))
{
if (exitHandler == null)
{
exitHandler = () => {
Environment.ExitCode = 1;
Environment.Exit(Environment.ExitCode);
};
}
exitHandler();
return default;
}
validated = validator(input);
if (!validated)
{
if (validationMessage == null)
{
validationMessage = "Please try again.";
}
Console.WriteLine(validationMessage);
}
} while (!validated);
return projector(input);
}
}
This allows us to make convenience methods for asking user input. The ones we need for our application are..
static string ReadNonEmptyString(string prompt)
{
return Read(prompt, input => !string.IsNullOrEmpty(input), input => input);
}
static string ReadString(string prompt)
{
return Read(prompt, input => true, input => input);
}
static int ReadInt32(string prompt)
{
return Read(prompt, input => int.TryParse(input, out var dummy), input => int.Parse(input));
}
With all these methods in place, we can now read a Person
from user input. Like suggested by others, the middle name is optional. The phone number is parsed as any string. As an exercise, you could extend the parser to allow for a ReadPhoneNumber
method.
internal static Person ReadPerson()
{
return new Person
{
FirstName = ReadNonEmptyString("Please enter your first name:"),
MiddleName = ReadString("Please enter your middle name (optional):"),
LastName = ReadNonEmptyString("Please enter your last name:"),
Age = ReadInt32("Please enter your age:"),
PhoneNumber = ReadNonEmptyString("Please enter your phone number:")
};
}
The last step is allowing for custom formatting and outputting to the console.
internal static void PersonInfo(Person person)
{
var hasMiddleName = !string.IsNullOrEmpty(person.MiddleName);
if (hasMiddleName)
{
Console.WriteLine($"Your name is: {person.FirstName} {person.MiddleName}. {person.LastName}");
Console.WriteLine($"Your initials are: {person.FirstName[0]}{person.MiddleName[0]}{person.LastName[0]}");
}
else
{
Console.WriteLine($"Your name is: {person.FirstName} {person.LastName}");
Console.WriteLine($"Your initials are: {person.FirstName[0]}{person.LastName[0]}");
}
Console.WriteLine($"Your age is: {person.Age}");
Console.WriteLine($"Your phone number is: {person.PhoneNumber}");
}
And some app info..
internal static void AppInfo()
{
Console.WriteLine("This program asks a person to enter full name, age, and phone number.");
Console.WriteLine("The person's information is then displayed together with the initials.");
Console.WriteLine("At any time the program could be terminated by typing: EXIT.");
}
This brings us to the application loop as shown at the start..
public static void Run()
{
UserPane.AppInfo();
while (true)
{
UserPane.PersonInfo(UserPane.ReadPerson());
}
}