How do I test if a type T implements IParsable<T>?
Asked Answered
P

2

5

.NET 7 recently introduced IParsable as an interface, and I'd like to check for its presence. Some test that will return true if T implements IParsable<T> and false otherwise.

Say I want to return an object that is of type T when T has a Parse method, for example:

T ParseAs<T>(string s)
{
    if (typeof(IParsable<T>).IsAssignableFrom(typeof(T)))
    {
        return T.Parse(s);
    }
    else
    {
        //do something else...
    }
}

I would hope this to check if T implements IParsable<T> and grant me access to the Parse and TryParse methods inside. I don't seem to be able to use T as a type argument for IParsable, instead receiving this exception:

CS0314
The type 'T' cannot be used as type parameter 'TSelf' in the generic type or method 'IParsable<TSelf>'. There is no boxing conversion or type parameter conversion from 'T' to 'System.IParsable<T>'

I also receive the above error if I try to use is:

s is IParsable<T>

How would I resolve this?

Pinnatiped answered 19/11, 2022 at 17:6 Comment(4)
Does this help? https://mcmap.net/q/108013/-how-to-determine-if-a-type-implements-a-specific-generic-interface-typePadraig
That compiles, but doesn't allow me to do T.Parse(s) - do you know why that is?Pinnatiped
IParsable is recursively defined, so you need to establish that T is IParsable<T> before you could say is IParsable<T>...Tenebrific
Am I understanding you correctly in saying that before I can check if T is IParsable<T>, I have to know that T is IParsable<T>? That sounds like a catch-22.Pinnatiped
T
6

To be able to use T.Parse() syntax - you need to know at compile time that T implements IParseable<T>. The only way you can be sure about that at compile time is to explicitly say that:

T ParseAs<T>(string s) where T: IParsable<T> {
    return T.Parse(s, null);
}

If you have just type T without explicitly saying it's IParsable<T> - it's not possible to use T.Parse syntax and you should use reflection all the way. That is first you check if T implements that interface via reflection, and then you again use reflection to call that static method:

T ParseAs<T>(string s) {
    var isParsable = typeof(T).GetInterfaces().Any(c => c.IsGenericType && c.GetGenericTypeDefinition() == typeof(IParsable<>));
    if (isParsable) {
        var parse = typeof(T).GetMethods(BindingFlags.Static | BindingFlags.Public)
            .FirstOrDefault(c => c.Name == "Parse" && c.GetParameters().Length == 2 && c.GetParameters()[0].ParameterType == typeof(string) && c.GetParameters()[1].ParameterType == typeof(IFormatProvider));
        if (parse != null)
            return (T) parse.Invoke(null, new object[] { s, null });
    }

    return default(T);
}

That's of course pretty ugly and you'll likely won't want to do that.

Twickenham answered 19/11, 2022 at 18:13 Comment(1)
The reflection approach was the one I was using prior to this update, and I was looking to replace it with something more elegant. Thank you for the answer - it exactly answers my question.Pinnatiped
R
0

If you're happy to use dynamic code then you can use the technique illustrated here:

static T ParseAs<T>(string s) {
    var isParsable = typeof(T).GetInterfaces().Any(c => c.IsGenericType && c.GetGenericTypeDefinition() == typeof(IParsable<>));
    if (isParsable) {
        return Parse(s, (dynamic)default(T));
    }

    return default(T);
}

 static T Parse<T>(string stringValue, T defaultValue) where T : IParsable<T>
 {
     return T.Parse(stringValue, null);
 }

Here the parameter named defaultValue exists purely to allow the compiler to perform type inference at runtime using the dynamic type.

If you're curious as to how this dynamic code performs compared with static invocations, or invocations by reflection, see the following benchmarks, run on .Net 8. In short, the dynamic invocation is 5 times faster than reflection, but 40 times slower than the static invocation.

Method Mean Error StdDev
ParseStatic 5.909 ns 0.1440 ns 0.1658 ns
ParseDynamic 208.928 ns 1.5338 ns 1.4347 ns
ParseReflection 1,060.491 ns 9.5423 ns 8.4590 ns
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<ParseBenchmark>(); ;

public class ParseBenchmark
{
    [Benchmark]
    public int ParseStatic() => int.Parse("1");

    [Benchmark]
    public int ParseDynamic() => ParseAsDynamic<int>("1");

    [Benchmark]
    public int ParseReflection() => ParseAsReflection<int>("1");
    
    T ParseAsReflection<T>(string s)
    {
        var isParsable = typeof(T).GetInterfaces().Any(c => c.IsGenericType && c.GetGenericTypeDefinition() == typeof(IParsable<>));
        if (isParsable)
        {
            var parse = typeof(T).GetMethods(BindingFlags.Static | BindingFlags.Public)
                .FirstOrDefault(c => c.Name == "Parse" && c.GetParameters().Length == 2 && c.GetParameters()[0].ParameterType == typeof(string) && c.GetParameters()[1].ParameterType == typeof(IFormatProvider));
            if (parse != null)
                return (T)parse.Invoke(null, new object[] { s, null });
        }

        return default(T);
    }

    static T ParseAsDynamic<T>(string s)
    {
        var isParsable = typeof(T).GetInterfaces().Any(c => c.IsGenericType && c.GetGenericTypeDefinition() == typeof(IParsable<>));
        if (isParsable)
        {
            return Parse(s, (dynamic)default(T));
        }

        return default(T);
    }

    static T Parse<T>(string stringValue, T defaultValue) where T : IParsable<T>
    {
        return T.Parse(stringValue, null);
    }
}
Roselba answered 5/6, 2024 at 16:14 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.