File: System\Reflection\Runtime\BindingFlagSupport\MemberPolicies.cs
Web Access
Project: src\src\libraries\System.Reflection.MetadataLoadContext\src\System.Reflection.MetadataLoadContext.csproj (System.Reflection.MetadataLoadContext)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection.TypeLoading;
using RuntimeTypeInfo = System.Reflection.TypeLoading.RoType;
 
namespace System.Reflection.Runtime.BindingFlagSupport
{
    /// <summary>
    /// This class encapsulates the minimum set of arcane .NET Framework CLR policies needed to implement the Get*(BindingFlags) apis.
    /// In particular, it encapsulates behaviors such as what exactly determines the "visibility" of a property and event, and
    /// what determines whether and how they are overridden.
    /// </summary>
    internal abstract class MemberPolicies<M> where M : MemberInfo
    {
        // Subclasses for specific MemberInfo types must override these:
 
        //
        // Returns all of the directly declared members on the given TypeInfo.
        //
        public abstract IEnumerable<M> GetDeclaredMembers(TypeInfo typeInfo);
 
        //
        // Returns all of the directly declared members on the given TypeInfo whose name matches filter. If filter is null,
        // returns all directly declared members.
        //
        public abstract IEnumerable<M> CoreGetDeclaredMembers(RuntimeTypeInfo type, NameFilter? filter, RuntimeTypeInfo reflectedType);
 
        //
        // Policy to decide whether a member is considered "virtual", "virtual new" and what its member visibility is.
        // (For "visibility", we reuse the MethodAttributes enum since Reflection lacks an element-agnostic enum for this.
        //  Only the MemberAccessMask bits are set.)
        //
        public abstract void GetMemberAttributes(M member, out MethodAttributes visibility, out bool isStatic, out bool isVirtual, out bool isNewSlot);
 
        //
        // Policy to decide whether "derivedMember" is a virtual override of "baseMember." Used to implement MethodInfo.GetBaseDefinition(),
        // parent chain traversal for discovering inherited custom attributes, and suppressing lookup results in the Type.Get*() api family.
        //
        // Does not consider explicit overrides (methodimpls.) Does not consider "overrides" of interface methods.
        //
        public abstract bool ImplicitlyOverrides(M? baseMember, M? derivedMember);
 
        //
        // Policy to decide how BindingFlags should be reinterpreted for a given member type.
        // This is overridden for nested types which all match on any combination Instance | Static and are never inherited.
        // It is also overridden for constructors which are never inherited.
        //
        public virtual BindingFlags ModifyBindingFlags(BindingFlags bindingFlags)
        {
            return bindingFlags;
        }
 
        //
        // Policy to decide if BindingFlags is always interpreted as having set DeclaredOnly.
        //
        public abstract bool AlwaysTreatAsDeclaredOnly { get; }
 
        //
        // Policy to decide how or if members in more derived types hide same-named members in base types.
        // Due to .NET Framework compat concerns, the definitions are a bit more arbitrary than we'd like.
        //
        public abstract bool IsSuppressedByMoreDerivedMember(M member, M[] priorMembers, int startIndex, int endIndex);
 
        //
        // Policy to decide whether to throw an AmbiguousMatchException on an ambiguous Type.Get*() call.
        // Does not apply to GetConstructor/GetMethod/GetProperty calls that have a non-null Type[] array passed to it.
        //
        // If method returns true, the Get() api will pick the member that's in the most derived type.
        // If method returns false, the Get() api throws AmbiguousMatchException.
        //
        public abstract bool OkToIgnoreAmbiguity(M m1, M m2);
 
        //
        // Helper method for determining whether two methods are signature-compatible.
        //
        protected static bool AreNamesAndSignaturesEqual(MethodInfo method1, MethodInfo method2)
        {
            if (method1.Name != method2.Name)
                return false;
 
            ParameterInfo[] p1 = method1.GetParametersNoCopy();
            ParameterInfo[] p2 = method2.GetParametersNoCopy();
            if (p1.Length != p2.Length)
                return false;
 
            bool isGenericMethod1 = method1.IsGenericMethodDefinition;
            bool isGenericMethod2 = method2.IsGenericMethodDefinition;
            if (isGenericMethod1 != isGenericMethod2)
                return false;
            if (!isGenericMethod1)
            {
                for (int i = 0; i < p1.Length; i++)
                {
                    Type parameterType1 = p1[i].ParameterType;
                    Type parameterType2 = p2[i].ParameterType;
                    if (!(parameterType1.Equals(parameterType2)))
                    {
                        return false;
                    }
                }
            }
            else
            {
                if (method1.GetGenericArguments().Length != method2.GetGenericArguments().Length)
                    return false;
                for (int i = 0; i < p1.Length; i++)
                {
                    Type parameterType1 = p1[i].ParameterType;
                    Type parameterType2 = p2[i].ParameterType;
                    if (!GenericMethodAwareAreParameterTypesEqual(parameterType1, parameterType2))
                    {
                        return false;
                    }
                }
            }
            return true;
        }
 
        //
        // This helper compares the types of the corresponding parameters of two methods to see if one method is signature equivalent to the other.
        // This is needed when comparing the signatures of two generic methods as Type.Equals() is not up to that job.
        //
        private static bool GenericMethodAwareAreParameterTypesEqual(Type t1, Type t2)
        {
            // Fast-path - if Reflection has already deemed them equivalent, we can trust its result.
            if (t1.Equals(t2))
                return true;
 
            // If we got here, Reflection determined the types not equivalent. Most of the time, that's the result we want.
            // There is however, one wrinkle. If the type is or embeds a generic method parameter type, Reflection will always report them
            // non-equivalent, since generic parameter type comparison always compares both the position and the declaring method. For our purposes, though,
            // we only want to consider the position.
 
            // Fast-path: if the types don't embed any generic parameters, we can go ahead and use Reflection's result.
            if (!(t1.ContainsGenericParameters && t2.ContainsGenericParameters))
                return false;
 
            if ((t1.IsArray && t2.IsArray) || (t1.IsByRef && t2.IsByRef) || (t1.IsPointer && t2.IsPointer))
            {
                if (t1.IsSZArray() != t2.IsSZArray())
                    return false;
 
                if (t1.IsArray && (t1.GetArrayRank() != t2.GetArrayRank()))
                    return false;
 
                return GenericMethodAwareAreParameterTypesEqual(t1.GetElementType()!, t2.GetElementType()!);
            }
 
            if (t1.IsConstructedGenericType)
            {
                // We can use regular old Equals() rather than recursing into GenericMethodAwareAreParameterTypesEqual() since the
                // generic type definition will always be a plain old named type and won't embed any generic method parameters.
                if (!(t1.GetGenericTypeDefinition().Equals(t2.GetGenericTypeDefinition())))
                    return false;
 
                Type[] ga1 = t1.GenericTypeArguments;
                Type[] ga2 = t2.GenericTypeArguments;
                if (ga1.Length != ga2.Length)
                    return false;
 
                for (int i = 0; i < ga1.Length; i++)
                {
                    if (!GenericMethodAwareAreParameterTypesEqual(ga1[i], ga2[i]))
                        return false;
                }
                return true;
            }
 
            if (t1.IsGenericMethodParameter() && t2.IsGenericMethodParameter())
            {
                // A generic method parameter. The DeclaringMethods will be different but we don't care about that - we can assume that
                // the declaring method will be the method that declared the parameter's whose type we're testing. We only need to
                // compare the positions.
                return t1.GenericParameterPosition == t2.GenericParameterPosition;
            }
 
            // If we got here, either t1 and t2 are different flavors of types or they are both simple named types or both generic type parameters.
            // Either way, we can trust Reflection's result here.
            return false;
        }
 
#pragma warning disable CA1810 // explicit static cctor
        static MemberPolicies()
        {
            Type t = typeof(M);
            if (t.Equals(typeof(FieldInfo)))
            {
                MemberTypeIndex = BindingFlagSupport.MemberTypeIndex.Field;
                Default = (MemberPolicies<M>)(object)(new FieldPolicies());
            }
            else if (t.Equals(typeof(MethodInfo)))
            {
                MemberTypeIndex = BindingFlagSupport.MemberTypeIndex.Method;
                Default = (MemberPolicies<M>)(object)(new MethodPolicies());
            }
            else if (t.Equals(typeof(ConstructorInfo)))
            {
                MemberTypeIndex = BindingFlagSupport.MemberTypeIndex.Constructor;
                Default = (MemberPolicies<M>)(object)(new ConstructorPolicies());
            }
            else if (t.Equals(typeof(PropertyInfo)))
            {
                MemberTypeIndex = BindingFlagSupport.MemberTypeIndex.Property;
                Default = (MemberPolicies<M>)(object)(new PropertyPolicies());
            }
            else if (t.Equals(typeof(EventInfo)))
            {
                MemberTypeIndex = BindingFlagSupport.MemberTypeIndex.Event;
                Default = (MemberPolicies<M>)(object)(new EventPolicies());
            }
            else if (t.Equals(typeof(Type)))
            {
                MemberTypeIndex = BindingFlagSupport.MemberTypeIndex.NestedType;
                Default = (MemberPolicies<M>)(object)(new NestedTypePolicies());
            }
            else
            {
                Debug.Fail("Unknown MemberInfo type.");
            }
        }
#pragma warning restore CA1810
 
        //
        // This is a singleton class one for each MemberInfo category: Return the appropriate one.
        //
        public static readonly MemberPolicies<M> Default = null!;
 
        //
        // This returns a fixed value from 0 to MemberIndex.Count-1 with each possible type of M
        // being assigned a unique index (see the MemberTypeIndex for possible values). This is useful
        // for converting a type reference to M to an array index or switch case label.
        //
        public static readonly int MemberTypeIndex;
    }
}