2

I've been messing around with generic methods for my own console applications, because some operations just seemed really common to me. I'm currently wandering if there's a way to constrain a method's input type to a specific set of types in hard code by means of listing/methods that they contain, which could mean that I don't have to suppress warnings in my code if the assumption is there. Here's my current code:

static T GetNumber<T>() where T : new()
{
    Type type = typeof(T);
    MethodInfo TryParse = (from item in type.GetMethods() where item.Name == "TryParse" select item).ElementAt(0);

    while(true)
    {
        string input = NotNullInput();
        object[] parameters = new object[] { input, new T() };
        #pragma warning disable CS8605
        if (!(bool)TryParse.Invoke(new T(), parameters))
            Console.WriteLine($"Invalid {type.Name} input");
        else
            return (T)parameters[1];
        #pragma warning restore CS8605
    }
}

Just makes sure the user puts in the correct format, and I know that it would probably only ever be used on types like int, float, double, long etc. I know all the types I've thought of using contained the TryParse method, so if there's a way to assume that, it would fix everything.

Also open to suggestions on how to improve the code because I'm still learning how to mess around with types and reflection.

Florian
  • 1,019
  • 6
  • 22
  • 1
    Can you use generic maths?: https://stackoverflow.com/a/73167868/3181933 – ProgrammingLlama Jun 08 '23 at 08:59
  • 2
    How about [`IParseable`](https://learn.microsoft.com/en-us/dotnet/api/system.iparsable-1?view=net-7.0)? – Sweeper Jun 08 '23 at 09:02
  • What .NET version are you targeting? I think it was .NET 7 (may have been 6, not sure) that added various interfaces that numeric types now implement for this very reason. If you're targeting an older version then you're pretty much out of luck. Looks like I was correct about it being new in .NET 7. Followed the link above and tried to change the version to .NET 6 and no documentation was available. – jmcilhinney Jun 08 '23 at 09:58

2 Answers2

2

There is a built-in interface that expresses the idea of "this type has a TryParse method" since .NET 7 - IParseable<T>.

You can constrain T to that type, and it will work not just for numbers, but any type that implements IParseable<T>. If you only want it to work for numbers, there is also the subinterface INumber<T> that you can use instead.

Example:

public static T ParseInputAsTypeUntilSuccessful<T>() where T: IParsable<T> {
    while (true) {
        if (T.TryParse(Console.ReadLine(), CultureInfo.InvariantCulture, out var result)) {
            return result;
        } else {
            Console.WriteLine($"Invalid {typeof(T).Name} input!");
        }
    }
}

Note that you need to specify an IFormatProvider or null, and here I have used CultureInfo.InvariantCulture.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
1

The caller can pass the method itself instead of the type (the type gets inferred).

If you write the method this way:

public static T GetNumber<T>(Func<string,T> func)
{
    while(true)
    {
        string input = NotNullInput();
        try
        {
            return func(input);
        }
        catch
        {
            Console.WriteLine($"Invalid {typeof(T).Name} input");
        }
    }
}

Then you'd call it like this:

var myInteger = GetNumber(int.Parse);

Or

var myDecimal = GetNumber(decimal.Parse);

Or even

byte[] myArray = GetNumber(Convert.FromBase64String);

Notice that passing the parsing delegate automatically defines T for you, so you don't have to pass T explicitly.

John Wu
  • 50,556
  • 8
  • 44
  • 80