.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?
CodePudding user response:
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.
CodePudding user response:
As mentioned, this is not supposed to be used at runtime, as it is a compile time feature.
In C SFINAE could be used to dispatch to correct method at compile time. To simulate somehow this in C#, potentially, we could exploit searching for extension method, as below. But this is very kludgy and I would not use it in real world code.
public static class UglyExtensionsDontUseAtWork {
public static T ParseAs<T>(this string s)
where T:IParsable<T>
{
return T.Parse(s, default);
}
public static T ParseAs<T>(this object _)
{
throw new InvalidOperationException("Cannot parse. Class does not implement IParsable<T>: " typeof(T));
}
}
...
Console.WriteLine($"IsParseable<int>: {"123".ParseAs<int>()}"); // Output: 123
Console.WriteLine($"IsParseable<int>: {"123".ParseAs<string>()}"); // throws