10
\$\begingroup\$

Here is my problem. Do I need to give a new identifier to each delegate I write of the following delegate type? like so: or could i use one delegate that accounts for any Datatype I need to use so i don't have to keep repeating the code?

Something that may or may not be relevant: I'm calling these functions to prompt user's input for new fields to add for records in a database. So database field types are the data types I'm dealing with, but I'm using this approach for learning purposes to try and minimize redundancy.

public static class stcHelper  // helper class
{
    public delegate T DelReturnType<T>();
    static public DelReturnType<int> UserInt = () =>
    {
        Console.Write("Enter integer: ");
        return int.Parse(Console.ReadLine());
    };
    static public DelReturnType<string> UserString = () =>
    {
        Console.Write("Enter string: ");
        return Console.ReadLine();
    };
    static public DelReturnType<AnotherDataType> UserAnotherDataType = () =>
    {
        Console.Write("Enter AnotherDataType: ");
        return SomeKindOfConversionFunction(Console.ReadLine());
    };
    // and so on for what could be 15 other data types ..
}

or is there a better way to reduce redundant code?

The code below is also part of the same class.

public static class stcHelper
{
    static public T InsistValidInput<T>(DelTypeReturn<T> MyInputAction)
    {
        bool bSuccess = true;
        do
        {
            try
            {
                return MyInputAction();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Invalid input!" + ex.ToString());
                bSuccess = false;
            }
        } while (!bSuccess);
        return default(T);
    }
}
\$\endgroup\$

1 Answer 1

18
\$\begingroup\$

If done cleverly, you can get away with a single method to get the input of any type:

static T GetInput<T>(string message, Converter<string, T> transform)
{
    Console.WriteLine(message);
    return transform(Console.ReadLine());
}

All you need to do is pass in a transformation function (the Converter<TInput,TOutput> delegate is equivalent to Func<T,TResult>):

int parsedInt = GetInput("Type in a number", int.Parse);
float parsedFloat = GetInput("Type in a float", float.Parse);

And ta-da, you've got your input, parsed as the desired type.

But as you also noted in the second half of your question, it's dangerous to assume that the input will always be valid. There's a universal version of your insist-on-valid-input function too. However, you should avoid using exceptions for normal control flow. Use the xxx.TryParse methods instead.

First, define the following delegate that matches the TryParse method defined in all the primitive types:

delegate bool TryParse<T>(string input, out T output);

Then, you can implement the loop:

static T InsistOnValidInput<T>(string message, string errorMessage, TryParse<T> validator)
{
    Console.WriteLine(message);
    T result;
    while (!validator(Console.ReadLine(), out result))
    {
        Console.WriteLine(errorMessage);
    }
    return result;
}

And call it:

bool parsedBool = InsistOnValidInput<bool>("Type in a bool", "Try again", bool.TryParse);

Unfortunately, C# doesn't infer the type parameter of the output argument, so you need to specify it manually (in this case, <bool>).

\$\endgroup\$
12
  • \$\begingroup\$ I am reading your code, and wondering why you have 2 generics inside the 'Func<string, T>' ? Also, when you call the transform Func, you don't include <>, but include () with a parameter inside it? \$\endgroup\$
    – Matt Rohde
    Commented Oct 31, 2012 at 1:29
  • 1
    \$\begingroup\$ Func<string,T> is a .NET delegate describing a method that takes a single argument of type string and returns a result of type T. float.Parse is such a method, so it can be used as a transform. It follows that we can call transform with the result of Console.ReadLine and it will return T. We don't need to explicitly specify type arguments when they can be inferred by the compiler. I strongly encourage you to step through the code in the debugger line for line - everything will make sense. \$\endgroup\$
    – Adam
    Commented Oct 31, 2012 at 1:51
  • \$\begingroup\$ I see. by the way, in MSDN Library, it says public delegate TResult Func<in T, out TResult>(T arg). Was your positioning of T for the second parameter out TResult instead of for the first parameter in T intentional? If yes, then does that mean that T is determined by the output of the given Func<in T, out TResult>? \$\endgroup\$
    – Matt Rohde
    Commented Oct 31, 2012 at 2:37
  • \$\begingroup\$ the use of in and out in this case is confusing, but it has nothing to do with input or output parameters; instead, it means that T is is contravariant (real argument can be less derived) and TResult is covariant (real argument can be more derived) as described on msdn. Be sure to take a look at the delegates section of the covariance and contravariance guide as well. \$\endgroup\$
    – Adam
    Commented Oct 31, 2012 at 10:17
  • 2
    \$\begingroup\$ There is actually a more specific delegate for conversion, called System.Converter<Tinput,Toutput>. It is ultimately just a Func<Tin,Tout>, but it is more specific to the task and can be re-used in several framework functions. \$\endgroup\$
    – Dan Lyons
    Commented Oct 31, 2012 at 17:15

Not the answer you're looking for? Browse other questions tagged or ask your own question.