【问题标题】:Calling Type.GetRuntimeMethod on an interface with a generic method returns null在具有泛型方法的接口上调用 Type.GetRuntimeMethod 返回 null
【发布时间】:2015-08-02 00:09:38
【问题描述】:

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

在那个项目中,我有一个名为 IMyInterface 的接口,它有一个带有通用参数 TGenericObject 的方法 DoSomething。我还有一个名为 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() });
    }
}

【问题讨论】:

  • MyClass 没有实现IMyInterface。这可能是问题吗:)
  • 不幸的是,没有:( MyClass 并不意味着从 IMyInterface 派生。MyClass 实例是调用通用 DoSomething 方法所需的参数。
  • 我在您的示例中没有看到任何实现 IMyInterface 的内容。您希望如何找到“具体”的运行时方法?
  • 我认为此时不需要 IMyInterface 的实现,因为我试图通过反射找到的是接口中声明的 DoSomething 方法的 MethodInfo :S 但是,假设你添加一个名为 MyInterfaceImpl 的 IMyInterface 实现,并在 Main 方法中将 typeof(IMyInterface) 替换为 typeof(MyInterfaceImpl)。不幸的是,您得到的仍然是相同的结果:methodInfo 为空。 (顺便说一句,我提供的示例编译:))
  • @Kzrystof GetRuntimeMethod 不应该解决重载问题,因为您没有声明这样的方法,所以找不到它。

标签: c# generics reflection windows-runtime


【解决方案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
}

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-04
      • 2019-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多