Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to invoke static interface method via reflection

Tags:

c#

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.

like image 399
Bogey Avatar asked Sep 11 '25 03:09

Bogey


1 Answers

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.

like image 98
Guru Stron Avatar answered Sep 13 '25 18:09

Guru Stron