Introduction
How many times do we write a console application where we need to insert some data? I write a lot of little utilities that ask for some input and automatize works for me. Every time I need to write the same code, with just a little variation.
So I write a method that relies on the Console
Class of .NET Framework and do the following operation:
- Write message to user
- Read answer from user
- Try to parse the data acquired and check for constraint (if any)
- If parse fails, go to step 1
Background
Here is a typical example:
We have a little TcpClient
that has to connect to different servers, each with a different Port
. So we need to ask the user for a valid port and
keep asking if the input does not meet
the constraint.
int port = 0;
string line = "";
do
{
Console.Write("Port: ");
line = Console.ReadLine();
if(string.IsNullOrWhitespace(line))
line = "5000";
}while(int.TryParse(line, out port) && (5000 <= port && port <= 5100));
I don't know if it's just me, but sometimes I need to write this snippet 5 or 6 times in a row and this can be very annoying. So I have decided to rewrite the previous code into a little snippet which is more generic possible and that does the job for me:
delegate bool TryParseDelegate<T>(string str, out T val);
static T Read<T>(string msg, TryParseDelegate<T> parser, T _default = default(T))
{
ConsoleColor oldColor = Console.ForegroundColor;
int attempt = 0;
string line;
T value;
do
{
if(attempt > 0)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Wrong Value: Retry!");
Console.ForegroundColor = oldColor;
}
attempt++;
Console.Write(msg + ": ");
line = Console.ReadLine();
if (string.IsNullOrWhiteSpace(line))
return _default;
}
while (!parser(line, out value));
return value;
}
Using the Code
I'm not completely satisfied with the use of a delegate, because I would have preferred a self-contained solution like the functors in "c", but with that snippet I can write really simple code like this:
var x = Read<double>("x value", double.TryParse);
which is really short, clear and let me write a lot of repetitive code faster and safer.
This snippet also works with a custom parser, like the one in the previous example:
var port = Read("Port[5000-5100]", (string str, out int val) =>
{
return int.TryParse(str, out val) && (5000 <= val && val <= 5100);
}, 5000);
and can handle really complex scenario:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public static bool TryParse(string str, out Person val)
{
int age;
val = null;
var args = str.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries)
.Select(arg => arg.Split(new[] { "=" }, StringSplitOptions.RemoveEmptyEntries))
.Where(arg => arg.Length == 2)
.ToDictionary(kv => kv[0], kv => kv[1]);
if (args.ContainsKey("FirstName") &&
args.ContainsKey("LastName") &&
args.ContainsKey("Age") && Int32.TryParse(args["Age"], out age))
{
val = new Person
{
FirstName = args["FirstName"],
LastName = args["LastName"],
Age = Int32.Parse(args["Age"])
};
return true;
}
return false;
}
}
static void Main(string[] args)
{
...
var person = Read<Person>("Person", Person.TryParse);
...
}
Points of Interest
Generics and Delegates can be used to make more reusable and flexible code, letting us create the "skeleton" for a method that we can adapt to the type of the data we have to compute. Thanks to these methods, I can write safer code and be sure I will not let users insert unvalidated values.
History
- v 1.0: First version, a lot of errors I guess!
- v 1.1: Better management of defaults and errors (Thanks to PIEBALDConsult)