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:
- Add ShortcutTypeAttribute to type containing the methods you want to call.
- Add ShortcutMethodAttribute to the method to be called (with key combination).
- Call RegisterAllAssemblyShortcuts()
- Determine the type of the active MDI form.
- Listen for keyboard input and check if shortcuts[mdiType] has any match.
- 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
forKeyOPressed() 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: