在具有泛型方法的接口上调用Type.GetRuntimeMethod将返回null

时间:2015-08-02 00:09:39

标签: c# generics reflection windows-runtime

我在我的一个C#项目中使用反射:它是针对Windows 8.1和Windows Phone 8.1的可移植类库。

在那个项目中,我有一个名为IMyInterface的接口,它有一个方法DoSomething,带有泛型参数TGenericObject。我还有一个名为MyClass的类。有一次,我需要通过反射在指定的接口中查找方法DoSomething。所以,我使用Type类中的GetRuntimeMethod方法和实际参数的类型,在我的例子中是MyClass。

请记住,我在这里提供的示例只是为了突出我所面临的问题。实际情况是IMyInterface接口和MyClass类在另一个项目中。

这是交易:我期待GetRuntimeMethod返回DoSomething方法的MethodInfo,但它没有:返回null。

从IMyInterface找到DoSomething方法或者我是否需要弄脏手段,是否有一些我很容易丢失的东西?

public interface IMyInterface
{
    void DoSomething<TGenericObject>(TGenericObject myGenericObject);
}

public class MyClass
{ }

class Program
{
    static void Main(string[] args)
    {
        MyClass myClassInst = new MyClass();

        MethodInfo methodInfo = typeof (IMyInterface).GetRuntimeMethod("DoSomething", new [] { myClassInst.GetType() });
    }
}

2 个答案:

答案 0 :(得分:1)

我能够编写自己的扩展方法,实际上是按照GetRuntimeMethod方法做的。困扰我的是我仍然不明白为什么.NET提供的GetRuntimeMethod方法在我的示例中返回null。

这是暂时解决我的问题的不完整的类。这是一种非常天真的方法,但它是一个起点。该课程中缺少很多东西,但至少,这是一个允许我继续学习的答案。

public static class TypeExtensions
{
    #region Public Methods

    /// <summary>
    /// Looks for the method in the type matching the name and arguments.
    /// </summary>
    /// <param name="type"></param>
    /// <param name="methodName">
    /// The name of the method to find.
    /// </param>
    /// <param name="args">
    /// The types of the method's arguments to match.
    /// </param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException">
    /// Thrown if:
    ///     - The name of the method is not specified.
    /// </exception>
    public static MethodInfo GetRuntimeMethod(this Type type, string methodName, Type[] args)
    {
        if (ReferenceEquals(type, null))
            throw new NullReferenceException("The type has not been specified.");

        if (string.IsNullOrEmpty(methodName))
            throw new ArgumentNullException("methodName", "The name of the method has not been specified.");


        var methods = type.GetRuntimeMethods().Where(methodInfo => string.Equals(methodInfo.Name, methodName, StringComparison.OrdinalIgnoreCase)).ToList();

        if (!methods.Any())
            return null;    //  No methods have the specified name.

        if (methods.Count == 1)
        {
            MethodInfo methodInfo = methods.Single();
            return IsSignatureMatch(methodInfo, args) ? methodInfo : null;
        }

        //  Oh noes, don't make me go there.
        throw new NotImplementedException("Resolving overloaded methods is not implemented as of now.");
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Finds out if the provided arguments matches the specified method's signature.
    /// </summary>
    /// <param name="methodInfo"></param>
    /// <param name="args"></param>
    /// <returns></returns>
    private static bool IsSignatureMatch(MethodBase methodInfo, Type[] args)
    {
        Debug.Assert(!ReferenceEquals(methodInfo, null), "The methodInfo has not been specified.");


        //  Gets the parameters of the method to analyze.
        ParameterInfo[] parameters = methodInfo.GetParameters();

        int currentArgId = 0;

        foreach (ParameterInfo parameterInfo in parameters)
        {
            if (!ReferenceEquals(args, null) && currentArgId < args.Length)
            {
                //  Find out if the types matchs.
                if (parameterInfo.ParameterType == args[currentArgId])
                {
                    currentArgId++;
                    continue; //  Yeah! Try the next one.
                }

                //  Is this a generic parameter?
                if (parameterInfo.ParameterType.IsGenericParameter)
                {
                    //  Gets the base type of the generic parameter.
                    Type baseType = parameterInfo.ParameterType.GetTypeInfo().BaseType;


                    //  TODO: This is not good v and works with the most simple situation.
                    //  Does the base type match?  
                    if (args[currentArgId].GetTypeInfo().BaseType == baseType)
                    {
                        currentArgId++;
                        continue; //  Yeah! Go on to the next parameter.
                    }
                }
            }

            //  Is this parameter optional or does it have a default value?
            if (parameterInfo.IsOptional || parameterInfo.HasDefaultValue)
                continue; // Uhum. So let's ignore this parameter for now.

            //  No need to go further. It does not match :(
            return false;
        }

        //  Ye!
        return true;
    }

    #endregion
}

答案 1 :(得分:0)

在.net标准中(不考虑性能):

public static MethodInfo ResolveMethod(this Type objType, string methodName, Type[] parameterTypes)
    {
        BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;

        List<MethodBase> regularMethods = new List<MethodBase>();
        List<MethodBase> genericMethods = new List<MethodBase>();

        foreach (MethodInfo methodInfo in objType.GetRuntimeMethods())
        {
            if (methodInfo.Name == methodName)
            {
                if (methodInfo.GetParameters().Length == parameterTypes.Length)
                {
                    if (methodInfo.IsGenericMethod)
                        genericMethods.Add(methodInfo);
                    else
                        regularMethods.Add(methodInfo);
                }
            }
        }

        MethodInfo found = null;

        if (regularMethods.Count > 0)
        {
            MethodBase[] regulaMethodsArray = regularMethods.ToArray();

            found = Type.DefaultBinder.SelectMethod(flags, regulaMethodsArray, parameterTypes, null) as MethodInfo;
        }

        if (found == null)
        {
            MethodBase[] genericMethodsArray = genericMethods.ToArray();

            foreach (MethodInfo method in genericMethods)
            {
                var templateTypes = GetTemplate(parameterTypes, method, out int genericCount);
                found = Type.DefaultBinder.SelectMethod(flags, genericMethodsArray, templateTypes, null) as MethodInfo;
                if (found != null)
                {
                    found = found.MakeGenericMethod(GetReplacements(parameterTypes, templateTypes, genericCount));
                    break;
                }
            }
        }

        return found;
    }

    public static Type[] GetReplacements(Type[] parameterTypes, Type[] template, int genericCount)
    {
        Type[] result = new Type[genericCount];
        int p = 0;

        for (int i = 0; i < parameterTypes.Length; i++)
        {
            if (template[i].IsGenericMethodParameter)
            {
                result[p] = parameterTypes[p];
                p++;
            }
        }

        return result;
    }

    public static Type[] GetTemplate(Type[] parameterTypes, MethodInfo methodInfo, out int genericCount)
    {
        genericCount = 0;
        Type[] result = new Type[parameterTypes.Length];
        ParameterInfo[] p = methodInfo.GetParameters();
        for (int i = 0; i < parameterTypes.Length; i++)
        {
            if (p[i].ParameterType.IsGenericParameter)
            {
                result[i] = Type.MakeGenericMethodParameter(i);
                genericCount++;
            }
            else
            {
                result[i] = parameterTypes[i];
            }
        }

        return result;
    }
}