【问题标题】:Resolving a member name at runtime在运行时解析成员名称
【发布时间】:2012-07-09 22:18:24
【问题描述】:

给定一个 type、一个 name 和一个 signature,我如何对名为 name 的成员进行成员查找 和签名 signature 使用 7.4 的 C# 规则(7.4 是 C# 语言规范中的章节编号)(或至少其中的一部分......假设我可以忍受完全匹配,没有转换/转换)在运行时?我需要得到一个MethodInfo/PropertyInfo/... 因为那样我必须通过反射来使用它(更准确地说,我正在尝试构建一个Expression.ForEach 构建器(一个能够创建表达式树的工厂表示 foreach 语句),并且要使用 C# foreach 实现像素完美,我必须能够进行鸭式输入并搜索 GetEnumerator 方法(在集合中),Current 属性和一个MoveNext 方法(在枚举器中),如 8.8.4 中所写)

问题(问题的一个例子)

class C1
{
    public int Current { get; set; }

    public object MoveNext()
    {
        return null;
    }
}

class C2 : C1
{
    public new long Current { get; set; }

    public new bool MoveNext()
    {
        return true;
    }
}

class C3 : C2
{
}

var m = typeof(C3).GetMethods(); // I get both versions of MoveNext()
var p = typeof(C3).GetProperties(); // I get both versions of Current

很明显,如果我尝试typeof(C3).GetProperty("Current"),我会得到AmbiguousMatchException 异常。

接口存在类似但不同的问题:

interface I0
{
    int Current { get; set; }
}

interface I1 : I0
{
    new long Current { get; set; }
}

interface I2 : I1, I0
{
    new object Current { get; set; }
}

interface I3 : I2
{
}

在这里,如果我尝试执行typeof(I3).GetProperties(),我没有得到Current 属性(这是已知的,例如参见GetProperties() to return all properties for an interface inheritance hierarchy),但我不能简单地扁平化接口,因为那时我不知道谁在隐瞒谁。

我知道这个问题可能在Microsoft.CSharp.RuntimeBinder 命名空间(在Microsoft.CSharp 程序集中声明)的某个地方得到了解决。这是因为我正在尝试使用 C# 规则,并且当您进行动态方法调用时,成员查找是必要的,但我无法找到任何东西(然后我会得到一个 Expression 或者可能是直接调用)。

经过一番思考,很明显Microsoft.VisualBasic 程序集中有类似的东西。 VB.NET 支持后期绑定。它在Microsoft.VisualBasic.CompilerServices.NewLateBinding 中,但它没有公开后期的有界方法。

【问题讨论】:

  • 我按照您的建议考虑使用Binder,但不知道该怎么做。您可以绑定到运行时类型的正确属性,但不能绑定到它继承的其他类型(如I3)。

标签: c# c#-4.0 reflection c#-5.0 overload-resolution


【解决方案1】:

使用带有BindingFlags参数的重载方法。

GetProperties(BindingFlags.DeclaredOnly)

【讨论】:

  • 哔!错误答案:-) 这就是空的C3 的原因。 CurrentC2 的一部分。
【解决方案2】:

(注意:阴影 = 隐藏 = C# 中方法/属性/事件定义中的新内容)

没有人回复,所以我会发布我在此期间编写的代码。我讨厌发布 400 行代码,但我想要完整。主要有两种方法:GetVisibleMethodsGetVisibleProperties。它们是Type 类的扩展方法。它们将返回一个类型的 public visible(非阴影/非覆盖)方法/属性。他们甚至应该处理 VB.NET 程序集(VB.NET 通常使用 hide-by-name 阴影而不是 C# 所做的 hidebysig)。他们将结果缓存在两个静态集合中(MethodsProperties)。该代码适用于 C# 4.0,所以我使用的是ConcurrentDictionary<T, U>。如果您使用的是 C# 3.5,则可以将其替换为 Dictionary<T, U>,但在读取和写入时必须使用 lock () { } 对其进行保护。

它是如何工作的?它通过递归工作(我知道递归通常很糟糕,但我希望没有人会创建一个 1.000 级的继承链)。

对于“真实”类型(非接口),它会向上走一级(使用递归,所以这一级可以向上一级,依此类推),并且从返回的方法/属性列表中删除它重载/隐藏的方法/属性。

对于接口,它有点复杂。 Type.GetInterfaces() 返回所有被继承的接口,不管是直接继承还是间接继承。对于这些接口中的每一个,都会计算声明的方法/属性列表(通过递归)。此列表与接口隐藏的方法/属性列表(HashSet<MethodInfo>/HashSet<PropertyInfo>)配对。这些被一个或另一个接口隐藏的方法/属性将从接口返回的所有其他方法/属性中删除(因此,如果您有 I1Method1(int)I2I1 继承,则重新声明 Method1(int) 和这样做隐藏I1.Method1I3继承自I1I2I2隐藏I1.Method1的事实将应用于从探索I1返回的方法,因此删除I1.Method1(int)(发生这种情况是因为我没有为接口生成继承映射,我只是看看隐藏了什么))。

从返回的方法/属性集合中,可以使用 Linq 找到查找的方法/属性。请注意,使用接口,您可以找到多个具有给定签名的方法/属性。一个例子:

interface I1
{
    void Method1();
}

interface I2
{
    void Method1();
}

interface I3 : I1, I2
{
}

I3 将返回两个Method1()

代码:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public static class TypeEx
{
    /// <summary>
    /// Type, Tuple&lt;Methods of type, (for interfaces)methods of base interfaces shadowed&gt;
    /// </summary>
    public static readonly ConcurrentDictionary<Type, Tuple<MethodInfo[], HashSet<MethodInfo>>> Methods = new ConcurrentDictionary<Type, Tuple<MethodInfo[], HashSet<MethodInfo>>>();

    /// <summary>
    /// Type, Tuple&lt;Properties of type, (for interfaces)properties of base interfaces shadowed&gt;
    /// </summary>
    public static readonly ConcurrentDictionary<Type, Tuple<PropertyInfo[], HashSet<PropertyInfo>>> Properties = new ConcurrentDictionary<Type, Tuple<PropertyInfo[], HashSet<PropertyInfo>>>();

    public static MethodInfo[] GetVisibleMethods(this Type type)
    {
        if (type.IsInterface)
        {
            return (MethodInfo[])type.GetVisibleMethodsInterfaceImpl().Item1.Clone();
        }

        return (MethodInfo[])type.GetVisibleMethodsImpl().Clone();
    }

    public static PropertyInfo[] GetVisibleProperties(this Type type)
    {
        if (type.IsInterface)
        {
            return (PropertyInfo[])type.GetVisiblePropertiesInterfaceImpl().Item1.Clone();
        }

        return (PropertyInfo[])type.GetVisiblePropertiesImpl().Clone();
    }

    private static MethodInfo[] GetVisibleMethodsImpl(this Type type)
    {
        Tuple<MethodInfo[], HashSet<MethodInfo>> tuple;

        if (Methods.TryGetValue(type, out tuple))
        {
            return tuple.Item1;
        }

        var methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);

        if (type.BaseType == null)
        {
            Methods.TryAdd(type, Tuple.Create(methods, (HashSet<MethodInfo>)null));
            return methods;
        }

        var baseMethods = type.BaseType.GetVisibleMethodsImpl().ToList();

        foreach (var method in methods)
        {
            if (method.IsHideByName())
            {
                baseMethods.RemoveAll(p => p.Name == method.Name);
            }
            else
            {
                int numGenericArguments = method.GetGenericArguments().Length;
                var parameters = method.GetParameters();

                baseMethods.RemoveAll(p =>
                {
                    if (!method.EqualSignature(numGenericArguments, parameters, p))
                    {
                        return false;
                    }

                    return true;
                });
            }
        }

        if (baseMethods.Count == 0)
        {
            Methods.TryAdd(type, Tuple.Create(methods, (HashSet<MethodInfo>)null));
            return methods;
        }

        var methods3 = new MethodInfo[methods.Length + baseMethods.Count];

        Array.Copy(methods, 0, methods3, 0, methods.Length);
        baseMethods.CopyTo(methods3, methods.Length);

        Methods.TryAdd(type, Tuple.Create(methods3, (HashSet<MethodInfo>)null));
        return methods3;
    }

    private static Tuple<MethodInfo[], HashSet<MethodInfo>> GetVisibleMethodsInterfaceImpl(this Type type)
    {
        Tuple<MethodInfo[], HashSet<MethodInfo>> tuple;

        if (Methods.TryGetValue(type, out tuple))
        {
            return tuple;
        }

        var methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);

        var baseInterfaces = type.GetInterfaces();

        if (baseInterfaces.Length == 0)
        {
            tuple = Tuple.Create(methods, new HashSet<MethodInfo>());
            Methods.TryAdd(type, tuple);
            return tuple;
        }

        var baseMethods = new List<MethodInfo>();

        var baseMethodsTemp = new MethodInfo[baseInterfaces.Length][];

        var shadowedMethods = new HashSet<MethodInfo>();

        for (int i = 0; i < baseInterfaces.Length; i++)
        {
            var tuple2 = baseInterfaces[i].GetVisibleMethodsInterfaceImpl();
            baseMethodsTemp[i] = tuple2.Item1;
            shadowedMethods.UnionWith(tuple2.Item2);
        }

        for (int i = 0; i < baseInterfaces.Length; i++)
        {
            baseMethods.AddRange(baseMethodsTemp[i].Where(p => !shadowedMethods.Contains(p)));
        }

        foreach (var method in methods)
        {
            if (method.IsHideByName())
            {
                baseMethods.RemoveAll(p =>
                {
                    if (p.Name == method.Name)
                    {
                        shadowedMethods.Add(p);
                        return true;
                    }

                    return false;
                });
            }
            else
            {
                int numGenericArguments = method.GetGenericArguments().Length;
                var parameters = method.GetParameters();

                baseMethods.RemoveAll(p =>
                {
                    if (!method.EqualSignature(numGenericArguments, parameters, p))
                    {
                        return false;
                    }

                    shadowedMethods.Add(p);
                    return true;
                });
            }
        }

        if (baseMethods.Count == 0)
        {
            tuple = Tuple.Create(methods, shadowedMethods);
            Methods.TryAdd(type, tuple);
            return tuple;
        }

        var methods3 = new MethodInfo[methods.Length + baseMethods.Count];

        Array.Copy(methods, 0, methods3, 0, methods.Length);
        baseMethods.CopyTo(methods3, methods.Length);

        tuple = Tuple.Create(methods3, shadowedMethods);
        Methods.TryAdd(type, tuple);
        return tuple;
    }

    private static PropertyInfo[] GetVisiblePropertiesImpl(this Type type)
    {
        Tuple<PropertyInfo[], HashSet<PropertyInfo>> tuple;

        if (Properties.TryGetValue(type, out tuple))
        {
            return tuple.Item1;
        }

        var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);

        if (type.BaseType == null)
        {
            Properties.TryAdd(type, Tuple.Create(properties, (HashSet<PropertyInfo>)null));
            return properties;
        }

        var baseProperties = type.BaseType.GetVisiblePropertiesImpl().ToList();

        foreach (var property in properties)
        {
            if (property.IsHideByName())
            {
                baseProperties.RemoveAll(p => p.Name == property.Name);
            }
            else
            {
                var indexers = property.GetIndexParameters();

                baseProperties.RemoveAll(p =>
                {
                    if (!property.EqualSignature(indexers, p))
                    {
                        return false;
                    }

                    return true;
                });
            }
        }

        if (baseProperties.Count == 0)
        {
            Properties.TryAdd(type, Tuple.Create(properties, (HashSet<PropertyInfo>)null));
            return properties;
        }

        var properties3 = new PropertyInfo[properties.Length + baseProperties.Count];

        Array.Copy(properties, 0, properties3, 0, properties.Length);
        baseProperties.CopyTo(properties3, properties.Length);

        Properties.TryAdd(type, Tuple.Create(properties3, (HashSet<PropertyInfo>)null));
        return properties3;
    }

    private static Tuple<PropertyInfo[], HashSet<PropertyInfo>> GetVisiblePropertiesInterfaceImpl(this Type type)
    {
        Tuple<PropertyInfo[], HashSet<PropertyInfo>> tuple;

        if (Properties.TryGetValue(type, out tuple))
        {
            return tuple;
        }

        var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);

        var baseInterfaces = type.GetInterfaces();

        if (baseInterfaces.Length == 0)
        {
            tuple = Tuple.Create(properties, new HashSet<PropertyInfo>());
            Properties.TryAdd(type, tuple);
            return tuple;
        }

        var baseProperties = new List<PropertyInfo>();

        var basePropertiesTemp = new PropertyInfo[baseInterfaces.Length][];

        var shadowedProperties = new HashSet<PropertyInfo>();

        for (int i = 0; i < baseInterfaces.Length; i++)
        {
            var tuple2 = baseInterfaces[i].GetVisiblePropertiesInterfaceImpl();
            basePropertiesTemp[i] = tuple2.Item1;
            shadowedProperties.UnionWith(tuple2.Item2);
        }

        for (int i = 0; i < baseInterfaces.Length; i++)
        {
            baseProperties.AddRange(basePropertiesTemp[i].Where(p => !shadowedProperties.Contains(p)));
        }

        foreach (var property in properties)
        {
            if (property.IsHideByName())
            {
                baseProperties.RemoveAll(p =>
                {
                    if (p.Name == property.Name)
                    {
                        shadowedProperties.Add(p);
                        return true;
                    }

                    return false;
                });
            }
            else
            {
                var indexers = property.GetIndexParameters();

                baseProperties.RemoveAll(p =>
                {
                    if (!property.EqualSignature(indexers, p))
                    {
                        return false;
                    }

                    shadowedProperties.Add(p);
                    return true;
                });
            }
        }

        if (baseProperties.Count == 0)
        {
            tuple = Tuple.Create(properties, shadowedProperties);
            Properties.TryAdd(type, tuple);
            return tuple;
        }

        var properties3 = new PropertyInfo[properties.Length + baseProperties.Count];

        Array.Copy(properties, 0, properties3, 0, properties.Length);
        baseProperties.CopyTo(properties3, properties.Length);

        tuple = Tuple.Create(properties3, shadowedProperties);
        Properties.TryAdd(type, tuple);
        return tuple;
    }

    private static bool EqualSignature(this MethodInfo method1, int numGenericArguments1, ParameterInfo[] parameters1, MethodInfo method2)
    {
        // To shadow by signature a method must have same name, same number of 
        // generic arguments, same number of parameters and same parameters' type
        if (method1.Name != method2.Name)
        {
            return false;
        }

        if (numGenericArguments1 != method2.GetGenericArguments().Length)
        {
            return false;
        }

        var parameters2 = method2.GetParameters();

        if (!parameters1.EqualParameterTypes(parameters2))
        {
            return false;
        }

        return true;
    }

    private static bool EqualSignature(this PropertyInfo property1, ParameterInfo[] indexers1, PropertyInfo property2)
    {
        // To shadow by signature a property must have same name, 
        // same number of indexers and same indexers' type
        if (property1.Name != property2.Name)
        {
            return false;
        }

        var parameters2 = property1.GetIndexParameters();

        if (!indexers1.EqualParameterTypes(parameters2))
        {
            return false;
        }

        return true;
    }

    private static bool EqualParameterTypes(this ParameterInfo[] parameters1, ParameterInfo[] parameters2)
    {
        if (parameters1.Length != parameters2.Length)
        {
            return false;
        }

        for (int i = 0; i < parameters1.Length; i++)
        {
            if (parameters1[i].IsOut != parameters2[i].IsOut)
            {
                return false;
            }

            if (parameters1[i].ParameterType.IsGenericParameter)
            {
                if (!parameters2[i].ParameterType.IsGenericParameter)
                {
                    return false;
                }

                if (parameters1[i].ParameterType.GenericParameterPosition != parameters2[i].ParameterType.GenericParameterPosition)
                {
                    return false;
                }
            }
            else if (parameters1[i].ParameterType != parameters2[i].ParameterType)
            {
                return false;
            }
        }

        return true;
    }

    private static bool IsHideByName(this MethodInfo method)
    {
        if (!method.Attributes.HasFlag(MethodAttributes.HideBySig) && (!method.Attributes.HasFlag(MethodAttributes.Virtual) || method.Attributes.HasFlag(MethodAttributes.NewSlot)))
        {
            return true;
        }

        return false;
    }

    private static bool IsHideByName(this PropertyInfo property)
    {
        var get = property.GetGetMethod();

        if (get != null && get.IsHideByName())
        {
            return true;
        }

        var set = property.GetSetMethod();

        if (set != null && set.IsHideByName())
        {
            return true;
        }

        return false;
    }
}

【讨论】:

  • 递归并不是“通常不好”。是的,它会破坏你的筹码,但这并不意味着你不应该使用它,只是在某些情况下你必须小心。
  • 哇,这为我节省了大量时间。谢谢!
猜你喜欢
  • 1970-01-01
  • 2010-10-26
  • 2013-12-14
  • 1970-01-01
  • 1970-01-01
  • 2016-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多