Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Read Method with Generics & Delegate

0.00/5 (No votes)
11 Jul 2016 1  
In a console application, there is often the need to ask (and validate) some data from users. For this reason, I have created a function that makes use of generics and delegates to speed up programming.

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:

  1. Write message to user
  2. Read answer from user
  3. Try to parse the data acquired and check for constraint (if any)
  4. 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 = "";
// repeat if any of this test fail
do
{
    // ask the user for data
    Console.Write("Port: ");
    // read user input
    line = Console.ReadLine();
    // if user "send" an empty string, default value
    // will be used
    if(string.IsNullOrWhitespace(line))
        line = "5000";
// try to parse data and to check if the result is
// between the permitted range
}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
    {
        // Error message (only after 1st attempt)
        if(attempt > 0)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Wrong Value: Retry!");
            Console.ForegroundColor = oldColor;
        }
        attempt++;

        // Query User for Data:
        Console.Write(msg + ": ");
        line = Console.ReadLine();

        // Default value (empty line)
        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)
{
    ...
    // Read Person from Console
    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)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here