如何使用反射在泛型类型中调用非泛型方法

How to call non-generic methods inside generic types using reflection

使用 .Net Framework 4.8。

我正在为我的 MDI WinForms 应用程序创建一个快捷方式系统,这样当您在某些表单上按下某些键时,您可以使用自定义属性调用方法。

对于上下文,属性如下所示,并将它们保存为快捷方式:

[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);
    }
}

我这样解析所有快捷方式并将它们保存为 Dictionary:

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;
}

要使用它,您需要将 ShortcutTypeAttribute 分配给一个类型,然后将 ShortcutMethodAttribute 分配给您要调用的方法,并将组合键作为参数传递。

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

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

总而言之,它是这样工作的:

  1. 将 ShortcutTypeAttribute 添加到包含您要调用的方法的类型。
  2. 将ShortcutMethodAttribute添加到要调用的方法中(使用组合键)。
  3. 调用 RegisterAllAssemblyShortcuts()
  4. 确定活动 MDI 窗体的类型。
  5. 侦听键盘输入并检查快捷方式[mdiType] 是否匹配。
  6. 如果有 ShortcutEntry 则赋值对象并调用 ShortcutEntry.Trigger()。

所有这些步骤都可以正常工作

当我尝试使用在泛型类型上声明的 ShortcutEntry.Trigger() 调用非泛型方法时出现问题,如下所示:

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

我得到的异常是:

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

我不知道为什么 KeyOPressed()MethodInfoMethodInfo.ContainsGenericParameters = true,当:

所以我无法在 KeyOPressed 的 MethodInfo

上调用 MakeGenericMethod()

如何在泛型类型中调用非泛型方法?

答案编辑:现在可以使用了

替换了通用的触发函数以重新计算 methodinfo。

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);
    }
}

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

这是因为 KeyOPressed 是在通用类型中声明的。您需要创建绑定泛型类型(即 KeyboundForm<SomeActualForm>)才能调用它。

一种方法是更改​​反射以仅支持绑定泛型类型:

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

这将捕获 non-generic 类型,如 SomeClass 并绑定泛型类型,如 SomeOtherClass : KeyboundForm<SomeFormType>,并标有相应的属性。

或检查 类 的继承属性 (GetCustomAttributes<ShortcutTypeAttribute>(true)),它们是绑定的泛型类型 (Type.IsConstructedGenericType == true)。

相关:

  • Invoke a non generic method with generic arguments defined in a generic class