-1

A long time ago in a galaxy far, far away, I posted an answer to this question here about input and such.

The question dealt with avoiding multiple ReadLine() statements to get user input and a way around it to clean up code. I posted a simple answer with a prompt method that displayed the prompt and returned the input. However, it always returned the string. Now, one could implement their own parsing to extract the information they wanted, but what if Generics could simplify things and return the type desired?

I thought it would be easy. So I tried to do it as a simple exercise. Turns out, I don't think I'm smart enough (oops).

I first tried a helper method inside a Generic SomeClass<T> like

public static T getInput(String prompt, Type T)
{
    //some stuff about printing the prompt
    String input = Console.In.ReadLine();
    return (T)input;
}

As many of you will no doubt see, this is faulty and returns the error "cannot cast String to type T."

My next approach was to use SomeClass<T> where T : String Again, many of you will see the fault here: String is sealed.

So: the question is, is there a way to use the method I've outlined and Generics to correctly grab user input and return it in the type requested, like int, String, or possibly a user-defined type?

Community
  • 1
  • 1
D. Ben Knoble
  • 4,273
  • 1
  • 20
  • 38
  • 3
    _"I'd like to avoid summarizing it"_ -- your preferences notwithstanding, each Stack Overflow question should stand on its own (except those closed as duplicates, of course). Please add enough detail to _this_ question so that it can be understood without having to go read through a different question. Feel free to leave the link there, if you think it provides some added value, but please do not present your question in a way such that referring to that link is _required_. – Peter Duniho Apr 16 '15 at 01:04
  • Ok. Im about to leave on a trip thing, but I will get to editing it to be a standalone. Also, @PeterDuniho, thanks for the advice. I don't always think of things like standalone questions and I appreciate the moderation to help others better understand me. – D. Ben Knoble Apr 16 '15 at 01:05
  • You can't even cast `string` to `int`, so this won't work with a simple cast. Also that `Type T` isn't doing anything.. except confusing up the code. `(T)` refers to the type parameter. – Blorgbeard Apr 16 '15 at 01:06
  • 2
    Note that based on what's here now, I would suggest using `Convert.ChangeType(input, typeof(T))`. But I don't know for sure that's what you want, because the question isn't clear. – Peter Duniho Apr 16 '15 at 01:06
  • you need a type T that knows how to cast to string...I don't know if that's possible because you can't inherit string and can't use where T : IStringCastable because string doesn't implement this interface – ro-E Apr 16 '15 at 01:11
  • This might be what you're looking for @BenKnoble http://stackoverflow.com/a/1833128/4780742 – Peter Luu Apr 16 '15 at 01:23
  • Actually yes thats precisely it. Thank you @PeterLuu – D. Ben Knoble Apr 16 '15 at 01:25
  • @PeterDuniho did the edit sufficiently address the issue or do I need more detail? – D. Ben Knoble Apr 19 '15 at 15:59

3 Answers3

1

I think Convert.ChangeType would probably fit the bill here, although it would fail horribly with the wrong input. Exercise for OP.

public static T GetInput<T>(string prompt)
{
    //some stuff about printing the prompt
    string input = Console.ReadLine();
    return (T)Convert.ChangeType(input, typeof(T));
}

So now:

float a = GetInput<float>("enter a float:");

You could probably harden the code by looking at this question and its answers.

Community
  • 1
  • 1
spender
  • 117,338
  • 33
  • 229
  • 351
  • Could copy the link you provided that `Convert` came from into the answer? – D. Ben Knoble Apr 16 '15 at 01:28
  • Supplementary: You cannot use Convert.ChangeType to get a string convert to user-defined type http://stackoverflow.com/a/3847463/1287352, and also need to take care of nullable types http://stackoverflow.com/q/3531318/1287352 – Eric Apr 16 '15 at 01:38
1

Note that conversion to certain types from a string is extremely reliant on culture settings. For instance DateTime parsing will be extremely hard to do. For numbers, decimal separators, currency symbols etc vary by culture.

There should be a retry mechanism for when the user mistypes or provides invalid input, notifying the user.

static T ReadInput<T>(string prompt)
{
    return ReadInput<T>(prompt, CultureInfo.CurrentCulture);
}
static T ReadInput<T>(string prompt, IFormatProvider formatProvider)
{
    bool validInput = false;
    T result = default(T);
    Console.WriteLine("Prompt");
    while (!validInput)
    {
        try
        {
            result = (T)Convert.ChangeType(Console.ReadLine(), typeof (T), formatProvider);
            validInput = true;
        }
        catch
        {
            Console.WriteLine("Could not interpret value, please try again.");
        }
    }
    return result;
}

Note that there is no way to make this user friendly. Consider defining a converter interface that can provide an error message when parsing fails as well as safely try to convert:

interface IConvertStringTo<T>
{
    bool TryGetValue(string input, IFormatProvider formatProvider, out T value);
    string ErrorMessage { get; }
}

class IntegerParser : IConvertStringTo<int>
{
    public bool TryGetValue(string input, IFormatProvider formatProvider, out int value)
    {
        return int.TryParse(input, NumberStyles.Integer, formatProvider, out value);
    }

    public string ErrorMessage
    {
        get { return "Please enter a number."; }
    }
}
Bas
  • 26,772
  • 8
  • 53
  • 86
0

Based on the answer from https://stackoverflow.com/a/1833128/4780742 by Tim Coker (who credits "Tuna Toksoz"), you can use TypeDescriptor to get a converter for any type. The reason why TypeDescriptor is preferable to Convert.ChangeType is because it can work for nullables and user-defined classes if the converter for the user-defined class is implemented.

public static class TConverter
{
    public static T ChangeType<T>(object value)
    {
        return (T)ChangeType(typeof(T), value);
    }
    public static object ChangeType(Type t, object value)
    {
        TypeConverter tc = TypeDescriptor.GetConverter(t);
        return tc.ConvertFrom(value);
    }
    public static void RegisterTypeConverter<T, TC>() where TC : TypeConverter
    {

        TypeDescriptor.AddAttributes(typeof(T), new TypeConverterAttribute(typeof(TC)));
    }
}

The RegisterTypeConverter is so that you don't have to use reflection if you plan on converting a specific type many times over.

Community
  • 1
  • 1
Peter Luu
  • 446
  • 2
  • 10