Home > front end >  How to call non-generic methods inside generic types using reflection
How to call non-generic methods inside generic types using reflection

Time:04-24

Using .Net Framework 4.8.

I'm creating a shortcut system for my MDI WinForms application, so you can invoke methods when you press certain keys on certain forms, using custom attributes.

For context, the attributes look like this, and save them as Shortcutentry:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class ShortcutMethodAttribute : Attribute
{
    public Keys[] Combination {get; set;}
    public ShortcutMethodAttribute(params Keys[] combination)
    {
        Combination = combination;
    }
}

[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public sealed class ShortcutTypeAttribute : Attribute
{

}

public class ShortcutEntry
{
    public ShortcutMethodAttribute Attribute { get; private set; }
    public object Object { get; set; }
    public Keys[] KeyCombination { get; set; }
    public MethodInfo MethodInfo { get; private set; }

    public ShortcutEntry(object @object, Keys[] keyCombination, MethodInfo methodInfo, ShortcutMethodAttribute attrib)
    {
        this.Object = @object;
        this.KeyCombination = keyCombination;
        this.MethodInfo = methodInfo;
        this.Attribute = attrib;
    }

    public void Trigger()
    {
        MethodInfo.Invoke(Object, null);
    }
}

I resolve all shortcuts like this and save them as a Dictionary<Type, ShortcutEntry>:

public Dictionary<Type, List<ShortcutEntry>> RegisterAllAssemblyShortcuts()
{
    var shortcuts = new Dictionary<Type, ShortcutEntry>();

    var types = Assembly.GetExecutingAssembly().GetTypes();
    var typesWithAttribute = types.Where(x => x.GetCustomAttributes<ShortcutTypeAttribute>(false).Any());

    foreach (var type in typesWithAttribute)
    {
        var methods = type.GetMethods().Where(x => x.GetCustomAttributes(typeof(ShortcutMethodAttribute), false).Length > 0);
        foreach (var method in methods)
        {
            var attributes = method.GetCustomAttributes(typeof(ShortcutMethodAttribute), false).OfType<ShortcutMethodAttribute>();
            if (attributes == null) continue;

            foreach (var attribute in attributes)
            {
                var se = new ShortcutEntry(
                    null,
                    attribute.KeyCombination,
                    method,
                    attribute
                    );
                if (!shortcuts.ContainsKey(type)) shortcuts.Add(type, new List<ShortcutEntry>);
                shortcuts[type].Add(se);
            }       
        }
    }
    return shortcuts;
}

To use it, you need to assign the ShortcutTypeAttribute to a type, and then ShortcutMethodAttribute to the method you want to call, with the key combination passed as parameter.

[ShortcutTypeAttribute]
public class SomeClass
{
    public void SomeMethodA()
    {
        // do something
    }

    [ShortcutMethodAttribute(Keys.O, keys.I)]
    public void SomeMethodB()
    {
        // do something
    }
}

To summarize, it works like this:

  1. Add ShortcutTypeAttribute to type containing the methods you want to call.
  2. Add ShortcutMethodAttribute to the method to be called (with key combination).
  3. Call RegisterAllAssemblyShortcuts()
  4. Determine the type of the active MDI form.
  5. Listen for keyboard input and check if shortcuts[mdiType] has any match.
  6. If there is a ShortcutEntry then assing the Object and call ShortcutEntry.Trigger().

All of this steps work fine

The problem arises when I try to call a non-generic method with ShortcutEntry.Trigger() that is declared on a generic type, like so:

[ShortcutTypeAttribute]
public class KeyboundForm<T> : Form where T : class
{
    [ShortcutMethodAttribute(Keys.O)]
    public virtual void KeyOPressed() {}
}

The exception I get is:

System.InvalidOperationException: 'Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.'

I don't know why the MethodInfo for KeyOPressed() has MethodInfo.ContainsGenericParameters = true, when:

  • MethodInfo.IsGenericMethod = false
  • MethodInfo.IsGenericMethodDefinition = false

So I can't call MakeGenericMethod() on the KeyOPressed's MethodInfo

How can I invoke a non-generic method in a generic type?

Answer Edit: now it's working

Replaced the Trigger Function to recalculate methodinfo when it was generic.

public void Trigger()
{
    if (MethodInfo.ContainsGenericParameters)
    {
        var type = Object.GetType();
        var methodinfo = type.GetMethod(MethodInfo.Name);
        methodinfo.Invoke(Object, null);
    }
    else
    {
         MethodInfo.Invoke(Object, null);
    }
}

CodePudding user response:

I don't know why the MethodInfo for KeyOPressed() has MethodInfo.ContainsGenericParameters == true`, when ...

This is because KeyOPressed is declared in a generic type. You need to create bound generic type (i.e. KeyboundForm<SomeActualForm>) to be able to invoke it.

One approach is to change your reflection to support only bound generic types:

var typesWithAttribute = types
    .Where(t => !t.ContainsGenericParameters)
    .Where(x => x.GetCustomAttributes<ShortcutTypeAttribute>(false).Any())

Which will capture non-generic types like SomeClass and bound generic types like SomeOtherClass : KeyboundForm<SomeFormType> marked with corresponding attribute.

Or check for inherited attributes (GetCustomAttributes<ShortcutTypeAttribute>(true)) for classes which are bound generic types (Type.IsConstructedGenericType == true).

Related:

  • Related