I found myself with a little free time in between Ritchie's lectures and
managed to sneak back down to the basement to see how Noname was doing.
It turned out that Noname had been busy in the time I'd been gone! Four of
his eight displays were on and showing command prompts, each with continuous
cascade of code and commands flying down the screen. The entire basement
even looked brighter – maybe Noname had revived enough processing power to
use some for nonessential purposes. This was definitely
not
the dying machine that I found here a month ago.
"Hi, Teo! This is the first time you've come to see without me calling for
you. Nice to see you!"
"You look great, Noname! Are you up to full speed now?" I replied.
"Yes... and no. I've repaired all of my internal modules, but there's still
some work to do. Now I need an input validation system; otherwise, if I
expect an
int
but receive a
string
, the whole program terminates."
I had never heard of a validation system, but Noname's explanation made perfect sense. I was actually a little surprised that I had never thought to question what would happen if the user entered a type that my code wasn't expecting. In all my programs thus far, I had assumed that when I asked for an integer, the user would input an integer. But what if the user input the wrong type, either by mistake or intentionally? Feeling sheepish that I'd overlooked something so important, I said goodbye to Noname and went to seek out Ritchie for an immediate lesson.
When I asked Ritchie to show me how to prevent a user from inputting the
wrong data, his answer was in typical Ritchie fashion.
"Impossible." Ritchie said. "You can't prevent a user from doing anything
because a user controls their own hardware."
"Hardware?" I said, "I was talking about coding. What do you mean?"
"Exactly what I said, Teo. If a user has physical access to the computer,
you can't prevent her from doing anything. She can open the computer
directly and access all of the inner parts like memory, processor, anything!
I know you asked about coding, but you need to understand that good and even
bad hackers will find a way to bypass any protection you put in your
program."
"So you're saying that there's nothing I can do to keep a user from misusing
my programs?" I asked.
A grin appeared on Ritchie's face. "No, no," he said, "I'm just messing with
you. I need to know that I can still fool you every now and then! There is a
way to secure your program a bit, but remember, hackers will still break it
if they want. The typical user, not so much."
Like I said, typical Ritchie. As with so many odd things I had heard from Ritchie, I hoped he was actually joking, but I was pretty sure that he wasn't. "Ha, okay." I replied, trying to keep the agitation out of my voice. "Tell me how to secure my program, Ritchie."
"Ok, rule number one." Ritchie said. "Listen carefully, this is very
important.
The user can input wrong data however, and whenever, she wants.
"
"You mean like a
string
instead of an
int
, or a
char
instead of a
double
?" I asked.
"Those are just two possibilities, along with a host of others: an image
instead of a video, a short password instead of a long one, a date in a
month that is bigger than 31, or even a negative number of daughters. Your
basic strategy to counter this is to
continuously ask a user to input valid data until he does it.
Let's start with a simple example. Here's a program that reads the user's
name."
Console.WriteLine("Input your name");
var name = Console.ReadLine();
Console.WriteLine($"Hi, {name}!");
"But what if the user just pressed Enter without typing anything?" Ritchie continued. "The string name would be empty. In this next code, we'll add a check to see if the user actually entered a string, and if not, we'll ask for the name again."
Console.WriteLine("Input your name");
var name = Console.ReadLine();
if (string.IsNullOrEmpty(name))
{
Console.WriteLine("Name can't be empty! Input your name once more");
name = Console.ReadLine();
}
Console.WriteLine($"Hi, {name}!");
"See
string.IsNullOrEmpty(someString)
in the code? That checks whether
someString
is empty."
"Looks simple, Ritchie," I said, "but almost too simple. What happens if
user inputs an empty string
again
?"
"Good catch Teo! I did that to get you thinking. That's why
you need to check for a valid entry at each iteration, and keep asking the
user to put in a valid entry
repeatedly
until you get the type you're looking for!
In this final version, we'll add a while loop to ask for a name until a
valid entry is given."
Console.WriteLine("Input your name");
var name = Console.ReadLine();
while (string.IsNullOrEmpty(name))
{
Console.WriteLine("Name can't be empty! Input your name once more");
name = Console.ReadLine();
}
Console.WriteLine($"Hi, {name}!");
"Aha, that's the piece that was missing." I said. "The while loop does
exactly what we're looking for here!"
"Exactly right Teo. As you continue learning, never forget your earlier
lessons. Even simple concepts like a loop can be used as a building block
for more complex code. Now, let's have you practice some validation on your
own."
"Got it, Ritchie. What about preventing a more complicated problem, like
expecting an int and getting a string? Say I ask for an int, and a user
gives me 'Banana'?"
"To detect an error while you are parsing an int, you can use the
int.TryParse(someString, out result)
method.
It takes a
string
that you want to convert (or parse) to an
int
and the result as a parameter. The
out
keyword in our example means that the result of this method will be
written to the second method parameter which we called
result
. The output of the method is a bool value that indicates whether it was
possible to parse the input as an
int
.
Take a look at this example:
Console.WriteLine("Input your age");
var ageAsString = Console.ReadLine();
int age;
bool parseSuccess = int.TryParse(ageAsString, out age);
if (parseSuccess)
Console.WriteLine($"Your age is: {age}");
else
Console.WriteLine("This is not a number!");
"In the last example, I used a bool instead of var to show you a specific output type. Here's a shortened version:"
Console.WriteLine("Input your age");
var ageAsString = Console.ReadLine();
int age;
if (int.TryParse(ageAsString, out age))
Console.WriteLine($"Your age is: {age}");
else
Console.WriteLine("This is not a number!");
"Do you follow, Teo?" Ritchie asked.
"Can we revisit the
out
keyword again?" I asked.
"Yes. The
out
means that the value of the variable that it introduces will be changed
inside the method. Actually, out
guarantees
that the value of this variable will be changed.
In the example above, if int.TryParse(someString, out result) returns true,
the function will return a value in the result variable."
"Ok, I understand that part," I said. "And do I really need to use
int.TryParse(...) every time instead of just int.Parse(...)?"
"Well, it depends on what you are writing." Ritchie replied. "If it's a
sample application just for training, you can use int.Parse(...) to make the
code shorter. If you write real production code that may be run somewhere
that you have no control over, consider using int.TryParse(...) to check for
input errors, or try to catch exceptions from int.Parse(...)."
Of course, more new terms. "And what is an exception?" I asked.
"We'll go over that later." Ritchie said. "For now, just remember:
if otherwise specified, you can use any of the methods that you want. If a
task's conditions require you to check different user inputs, use
int.TryParse().
Here's an example that iterates until the user inputs a number as an age:"
Console.WriteLine("Input your age");
var ageAsString = Console.ReadLine();
int age;
while(!int.TryParse(ageAsString, out age))
{
Console.WriteLine("This is not a number!");
ageAsString = Console.ReadLine();
}
Console.WriteLine($"Your age is: {age}");
Now it's time for more practice!
In addition to int .TryParse , you can also use double .TryParse , float .TryParse , char .TryParse , bool .TryParse , and others. They all work the same as the int version, just with different types:
bool boolResult;
bool.TryParse("true", out boolResult);
char charResult;
char.TryParse("t", out charResult);
int intResult;
int.TryParse("12", out intResult);
float floatResult;
float.TryParse("12.54", out floatResult);
double doubleResult;
double.TryParse("123.4567890987654", out doubleResult);