Search code examples
c#reflection

How to invoke static interface method via reflection


How can you implement a function as follows?

static object GetZero(Type t)
{
  // If t implements INumberBase<TSomething>, then return INumberBase<TSomething>.Zero
  // Otherwise, throw some arbitrary Exception
}

My attempt so far was:

static object GetZero(Type t)
{
   return t
    .GetInterfaces()
    .Single(x => x.IsGenericType && !x.IsGenericTypeDefinition && x.GetGenericTypeDefinition() == typeof(INumberBase<>))
    .GetProperty("Zero", BindingFlags.Public | BindingFlags.Static)
    .GetValue(null);
}

However, under dotnet7, this fails with a fairly nasty exception. For example, GetZero(typeof(int)) throws

System.BadImageFormatException : Bad IL format. at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.PropertyInfo.GetValue(Object obj)

Why is this implementation throwing? Is there another way to implement this via reflection?


Edit: I can work around this by adding a static method as follows

 static T Zero<T>() where T : INumberBase<T>
 {
     return T.Zero;
 }

Then having my reflection code invoke this instead of the interface property. This works, but seems .. ugly? So I'm still curious why the original code above throws.


Solution

  • Static abstract interface members can be invoked only from concrete type, i.e. something like var i = INumberBase<int>.Zero; (which your reflection code basically attempts to do) is invalid and will not compile.

    Personally I prefer the generic indirection approach (the Zero<T> one, you can combine it with the reflection if needed) as far more robust, but pure reflection approach can be something like the following:

    static object GetZero(Type t) => t
            .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
            .Where(info => info.Name.EndsWith(".Zero") || info.Name = "Zero")
            .Single()
            .GetValue(null);
    

    Though it relies on the implementation details and is quite brittle (you can improve it by checking if type is INumberBase<> and searching first for member with name like INumberBase< TYPE_NAME >.Zero and then for name = Zero, but this quickly becomes more ugly than the generic approach).

    See also this answer.