通用方法

时间:2017-10-11 14:55:52

标签: c# generics reflection

出于测试目的,我要检查引用类中的一系列方法签名是否已在不同的静态类上实现。对于他们中的大多数人来说,以下是有效的:

private static IEnumerable<Signature> GetMethodSigs(Type type)
{
    // Get MethodInfos, filter and project into signatures
    var methods = type.GetMethods(
                          BindingFlags.Public
                        | BindingFlags.DeclaredOnly
                        | BindingFlags.Static 
                        | BindingFlags.Instance)
                    .Where(mi => !mi.Name.StartsWith("get_"))
                    .Where(mi => !mi.Name.StartsWith("set_"))
                    .Select(o => new Signature(o.Name, o.ReturnType, o.GetParameters().Select(pi => pi.ParameterType)));

    return methods;
}

private static MethodInfo FindMethod(Type type, Signature sig)
{
    MethodInfo member = type.GetMethod(
                                sig.Name,
                                BindingFlags.Public | BindingFlags.Static,
                                null,
                                sig.ParameterTypes.ToArray(),
                                null);
    return member;
}

public struct Signature
{
    public string Name;
    public Type ReturnType;
    public IEnumerable<Type> ParameterTypes;

    public Signature(string name, Type returnType, IEnumerable<Type> parameterTypes = null)
    {
        Name = name;
        ReturnType = returnType;
        ParameterTypes = parameterTypes;
    }
}

这是测试类的一部分,但想象一下驱动该过程的以下代码:

foreach(var sig in GetMethodSigs(typeof(ReferenceClass)))
    Console.WriteLine(FindMethod(typeof(TestClass), sig)?.ToString());

ReferenceClass上正好接受以下方法签名,但FindMethod()未找到TestClass上的等效方法(确实存在!):

public static void SomeMethod<T>(SomeDelegate<T> del)

GetMethods()为我提供了del参数(SomeDelegate`1)的类型,但这显然不适合在GetMethod()中搜索,{{1 }}为此输入返回FindMethod()

任何想法如何操纵null返回的值来使用GetMethods()搜索泛型方法(明显的细微之处在于委托中使用泛型参数,而不是简单的参数型)?

(我知道有GetMethod()的版本只是取名,但由于某些方法名称被重载,我还需要搜索参数类型。)

3 个答案:

答案 0 :(得分:1)

我很长一段时间没有注意到你在比较两种不同的类定义:

foreach(var sig in GetMethodSigs(typeof(ReferenceClass)))
    Console.WriteLine(FindMethod(typeof(TestClass), sig)?.ToString());

这是值得注意的,因为如果类是相同的,您的上述代码将起作用。问题是处理泛型的方式(简要查看Type源,看来Equals使用引用比较,但我认为==被卸载到编译器。重点是,不同的泛型方法类型不是参考等价物。但是验证实际发生的事情是留给读者的练习。

快速演示(您可以在交互式shell中运行)

public class D { public static void Method<T>(Action<T> t) { } }
(new D()).GetType().GetMethod("Method").GetParameters()[0].ParameterType == (typeof(Action<>))

输出:

false  

但是,如果检查类型,两者看起来都是一样的:

> (new D()).GetType().GetMethod("Method").GetParameters()[0].ParameterType.ToString()
"System.Action`1[T]"
> (typeof(Action<>)).ToString()
"System.Action`1[T]"  

好的,这就是问题演示。您应该能够通过更改GetMethodSigs中的select语句来解决此问题,以便在每个参数类型上调用GetGenericTypeDefinition()

.Select(o => new Signature(o.Name, o.ReturnType, o.GetParameters().Select(pi => pi.ParameterType.GetGenericTypeDefinition())));  

您可以看到以下内容现在显示相等:

> (new D()).GetType().GetMethod("Method").GetParameters()[0].ParameterType.GetGenericTypeDefinition() == (typeof(Action<>)).GetGenericTypeDefinition()
true   

当然,这仅适用于通用类型。你必须添加一些逻辑,只在上面的select语句中调用.GetGenericTypeDefinition()

来自https://stackoverflow.com/a/1855248/1462295

的更多信息

编辑:

使用https://stackoverflow.com/a/7182379/1462295

中的代码

(请注意上面的空课T

public class D
{
    public static void Method<TT>(Action<TT> t) { }
    public static void Method<TT>(TT t) { }
}

public class RefD
{
    public static void Method<TT>(Action<TT> t) { }
    public static void Method<TT>(TT t) { }
}

foreach(var sig in GetMethodSigs(typeof(RefD)))
    Console.WriteLine(GetMethodExt(typeof(D), sig.Name, sig.ParameterTypes.ToArray())?.ToString());

输出

Void Method[TT](System.Action`1[TT])
Void Method[TT](TT)  

答案 1 :(得分:1)

而不是Type.GetMethod()重载中的一个,我最终在目标类上使用GetMethods()并使用扩展方法循环遍历所有成员以比较每个参数类型(注意C#7仅本地功能):

public static bool Similar(this Type reference, Type type)
{
    if (reference.IsGenericParameter && type.IsGenericParameter)
    {
        return reference.GenericParameterPosition == type.GenericParameterPosition;
    }

    return ComparableType(reference) == ComparableType(type);

    Type ComparableType(Type cType)
        => cType.IsGenericType ? cType.GetGenericTypeDefinition() : cType;
}

如果符合以下条件,则认为两种类型是“相似的”:

  • 它们是简单类型,使用==运算符
  • 进行比较
  • 它们是泛型类型,并且在泛型参数列表中具有相同索引的类型(即在SomeMethod<T,S>(S parameter)中,唯一的参数类型将被视为等于SomeMethod<T1,T2>(T2 parm)中的参数类型但 SomeMethod<T,S>(T parameter))。
  • 它们是具有嵌套通用参数的相同类型。在这种情况下,注意到它是泛型类型的事实,但没有进一步探测参数(因此SomeMethod<T,S>(Action<T> parameter)上的参数类型将与SomeMethod<T,S>(Action<S> parameter)“相似”。

这并不理想,但事实证明这是一个非常难的问题!它适用于我的用例,它涵盖了我的所有案例,并且由于项目的性质(分析遗留代码),不会出现新的案例。

Similar()用于Type的以下扩展程序,旨在替换Type.GetMethod()并实现我上面提到的循环:

public static MethodInfo GetMethodWithGenerics(
                            this Type type,
                            string name, Type[] parameters,
                            BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
{
    var methods = type.GetMethods(flags);

    foreach (var method in methods)
    {
        var parmeterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();

        if (method.Name == name && parmeterTypes.Count() == parameters.Length)
        {
            bool match = true;

            for (int i = 0; i < parameters.Length; i++)
                match &= parmeterTypes[i].Similar(parameters[i]);

            if (match)
                return method;
        }
    }

    return null;
}

正如BurnsBA在他的回答下面说的那样,对于泛型的内置反射支持似乎存在一些基本问题,似乎没有一个简单的解决方案来解决我原来的问题。我在考虑了BurnBA的答案之后得到了这个答案,并在另一个问题上考虑了he linked to。这个答案对于希望产生更全面的比较版本的人来说特别有用。

任何发现这个有用的人都应该考虑提升其中一个或两个。

答案 2 :(得分:0)

你可以试试像

这样的东西
string name = "MyFunction";
Type type = typeof(Program);

MethodInfo member = type.GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance)
.Single(m => m.Name == name && m.GetParameters().Select(p => p.ParameterType.Name).SequenceEqual(new [] { typeof(MyClass<>).Name }));

public void MyFunction<T>(MyClass<T> test){}