Click here to Skip to main content
15,891,993 members
Articles / Programming Languages / C#
Tip/Trick

Read Method with Generics & Delegate

Rate me:
Please Sign up or sign in to vote.
4.71/5 (10 votes)
11 Jul 2016CPOL2 min read 12.5K   10   6
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.

C#
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:

C#
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:

C#
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
John Trinder12-Jul-16 11:27
professionalJohn Trinder12-Jul-16 11:27 
GeneralThoughts Pin
PIEBALDconsult9-Jul-16 6:20
mvePIEBALDconsult9-Jul-16 6:20 
AnswerRe: Thoughts Pin
Alberto Nuti9-Jul-16 9:21
Alberto Nuti9-Jul-16 9:21 
GeneralRe: Thoughts Pin
PIEBALDconsult9-Jul-16 10:06
mvePIEBALDconsult9-Jul-16 10:06 
GeneralMy vote of 5 Pin
csharpbd9-Jul-16 4:15
professionalcsharpbd9-Jul-16 4:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.