File: src\libraries\System.Private.CoreLib\src\System\RuntimeType.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// 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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace System
{
    internal sealed partial class RuntimeType : TypeInfo, ICloneable
    {
        public override Assembly Assembly => RuntimeTypeHandle.GetAssembly(this);
        public override Type? BaseType => GetBaseType();
        public override bool IsGenericParameter => RuntimeTypeHandle.IsGenericVariable(this);
        public override bool IsTypeDefinition => RuntimeTypeHandle.IsTypeDefinition(this);
        public override bool IsSecurityCritical => true;
        public override bool IsSecuritySafeCritical => false;
        public override bool IsSecurityTransparent => false;
        public override MemberTypes MemberType => (IsPublic || IsNotPublic) ? MemberTypes.TypeInfo : MemberTypes.NestedType;
        public override int MetadataToken => RuntimeTypeHandle.GetToken(this);
        public override Module Module => GetRuntimeModule();
        public override Type? ReflectedType => DeclaringType;
        public override RuntimeTypeHandle TypeHandle
        {
            [Intrinsic] // to avoid round-trip "handle -> RuntimeType -> handle" in JIT
            get => new RuntimeTypeHandle(this);
        }
 
        public override Type UnderlyingSystemType => this;
 
        public object Clone() => this;
 
        public override bool Equals(object? obj)
        {
            // ComObjects are identified by the instance of the Type object and not the TypeHandle.
            return obj == (object)this;
        }
 
        public override int GetArrayRank()
        {
            if (!IsArrayImpl())
                throw new ArgumentException(SR.Argument_HasToBeArrayClass);
 
            return RuntimeTypeHandle.GetArrayRank(this);
        }
 
        protected override TypeAttributes GetAttributeFlagsImpl() => RuntimeTypeHandle.GetAttributes(this);
 
        public override object[] GetCustomAttributes(bool inherit)
        {
            return CustomAttribute.GetCustomAttributes(this, ObjectType, inherit);
        }
 
        public override object[] GetCustomAttributes(Type attributeType, bool inherit)
        {
            ArgumentNullException.ThrowIfNull(attributeType);
 
            if (attributeType.UnderlyingSystemType is not RuntimeType attributeRuntimeType)
                throw new ArgumentException(SR.Arg_MustBeType, nameof(attributeType));
 
            return CustomAttribute.GetCustomAttributes(this, attributeRuntimeType, inherit);
        }
 
        public override IList<CustomAttributeData> GetCustomAttributesData()
        {
            return RuntimeCustomAttributeData.GetCustomAttributesInternal(this);
        }
 
        // GetDefaultMembers
        // This will return a MemberInfo that has been marked with the [DefaultMemberAttribute]
        [DynamicallyAccessedMembers(
            DynamicallyAccessedMemberTypes.PublicFields
            | DynamicallyAccessedMemberTypes.PublicMethods
            | DynamicallyAccessedMemberTypes.PublicEvents
            | DynamicallyAccessedMemberTypes.PublicProperties
            | DynamicallyAccessedMemberTypes.PublicConstructors
            | DynamicallyAccessedMemberTypes.PublicNestedTypes)]
        public override MemberInfo[] GetDefaultMembers()
        {
            // See if we have cached the default member name
            MemberInfo[]? members = null;
 
            string? defaultMemberName = GetDefaultMemberName();
            if (defaultMemberName != null)
            {
                members = GetMember(defaultMemberName);
            }
 
            return members ?? Array.Empty<MemberInfo>();
        }
 
        public override Type GetElementType() => RuntimeTypeHandle.GetElementType(this);
 
        public override string? GetEnumName(object value)
        {
            ArgumentNullException.ThrowIfNull(value);
 
            RuntimeType valueType = (RuntimeType)value.GetType();
 
            if (!(valueType.IsActualEnum || IsIntegerType(valueType)))
                throw new ArgumentException(SR.Arg_MustBeEnumBaseTypeOrEnum, nameof(value));
 
            // Map the value to a ulong and then look up that value in the enum.
            // This supports numerical values of different types than the enum
            // or its underlying type.
            return Enum.GetName(this, Enum.ToUInt64(value));
        }
 
        private static void ThrowMustBeEnum() =>
            throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");
 
        public override string[] GetEnumNames()
        {
            if (!IsActualEnum)
                ThrowMustBeEnum();
 
            string[] ret = Enum.GetNamesNoCopy(this);
 
            // Make a copy since we can't hand out the same array since users can modify them
            return new ReadOnlySpan<string>(ret).ToArray();
        }
 
        [RequiresDynamicCode("It might not be possible to create an array of the enum type at runtime. Use Enum.GetValues<T> or the GetEnumValuesAsUnderlyingType method instead.")]
        public override Array GetEnumValues()
        {
            if (!IsActualEnum)
                ThrowMustBeEnum();
 
            // Get all of the values as the underlying type and copy them to a new array of the enum type.
            Array values = Enum.GetValuesAsUnderlyingTypeNoCopy(this);
            Array ret = Array.CreateInstance(this, values.Length);
            Array.Copy(values, ret, values.Length);
 
            return ret;
        }
 
        /// <summary>
        /// Retrieves an array of the values of the underlying type constants in a specified enumeration type.
        /// </summary>
        /// <remarks>
        /// This method can be used to get enumeration values when creating an array of the enumeration type is challenging.
        /// For example, <see cref="T:System.Reflection.MetadataLoadContext" /> or on a platform where runtime codegen is not available.
        /// </remarks>
        /// <returns>An array that contains the values of the underlying type constants in enumType.</returns>
        /// <exception cref="ArgumentException">
        /// Thrown when the type is not Enum
        /// </exception>
        public override Array GetEnumValuesAsUnderlyingType()
        {
            if (!IsActualEnum)
                ThrowMustBeEnum();
 
            return Enum.GetValuesAsUnderlyingType(this);
        }
 
        public override Type GetEnumUnderlyingType()
        {
            if (!IsActualEnum)
                ThrowMustBeEnum();
 
            return Enum.InternalGetUnderlyingType(this);
        }
 
        public override int GetHashCode() => RuntimeHelpers.GetHashCode(this);
 
        internal RuntimeModule GetRuntimeModule() => RuntimeTypeHandle.GetModule(this);
 
        protected override TypeCode GetTypeCodeImpl()
        {
            TypeCode typeCode = Cache.TypeCode;
 
            if (typeCode != TypeCode.Empty)
                return typeCode;
 
            typeCode = GetRuntimeTypeCode(this);
            Cache.TypeCode = typeCode;
 
            return typeCode;
        }
 
        protected override bool HasElementTypeImpl() => RuntimeTypeHandle.HasElementType(this);
 
        protected override bool IsArrayImpl() => RuntimeTypeHandle.IsArray(this);
 
        protected override bool IsContextfulImpl() => false;
 
        public override bool IsDefined(Type attributeType, bool inherit)
        {
            ArgumentNullException.ThrowIfNull(attributeType);
 
            if (attributeType.UnderlyingSystemType is not RuntimeType attributeRuntimeType)
                throw new ArgumentException(SR.Arg_MustBeType, nameof(attributeType));
 
            return CustomAttribute.IsDefined(this, attributeRuntimeType, inherit);
        }
 
        public override bool IsEnumDefined(object value)
        {
            ArgumentNullException.ThrowIfNull(value);
 
            if (!IsActualEnum)
                ThrowMustBeEnum();
 
            // If the value is an Enum then we need to extract the underlying value from it
            RuntimeType valueType = (RuntimeType)value.GetType();
            if (valueType.IsActualEnum)
            {
                // The enum type must match this type.
                if (!valueType.IsEquivalentTo(this))
                    throw new ArgumentException(SR.Format(SR.Arg_EnumAndObjectMustBeSameType, valueType, this));
 
                valueType = (RuntimeType)valueType.GetEnumUnderlyingType();
            }
 
            // If a string is passed in, search the enum names with it.
            if (valueType == StringType)
                return Array.IndexOf(Enum.GetNamesNoCopy(this), (string)value) >= 0;
 
            // If an enum or integer value is passed in
            if (!IsIntegerType(valueType))
                throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType);
 
            RuntimeType underlyingType = Enum.InternalGetUnderlyingType(this);
            if (underlyingType != valueType)
                throw new ArgumentException(SR.Format(SR.Arg_EnumUnderlyingTypeAndObjectMustBeSameType, valueType, underlyingType));
 
            return GetTypeCode(underlyingType) switch
            {
                TypeCode.SByte => Enum.IsDefinedPrimitive(this, (byte)(sbyte)value),
                TypeCode.Byte => Enum.IsDefinedPrimitive(this, (byte)value),
                TypeCode.Int16 => Enum.IsDefinedPrimitive(this, (ushort)(short)value),
                TypeCode.UInt16 => Enum.IsDefinedPrimitive(this, (ushort)value),
                TypeCode.Int32 => Enum.IsDefinedPrimitive(this, (uint)(int)value),
                TypeCode.UInt32 => Enum.IsDefinedPrimitive(this, (uint)value),
                TypeCode.Int64 => Enum.IsDefinedPrimitive(this, (ulong)(long)value),
                TypeCode.UInt64 => Enum.IsDefinedPrimitive(this, (ulong)value),
                TypeCode.Single => Enum.IsDefinedPrimitive(this, (float)value),
                TypeCode.Double => Enum.IsDefinedPrimitive(this, (double)value),
                TypeCode.Char => Enum.IsDefinedPrimitive(this, (char)value),
                _ =>
                    underlyingType == typeof(nint) ? Enum.IsDefinedPrimitive(this, (nuint)(nint)value) :
                    underlyingType == typeof(nuint) ? Enum.IsDefinedPrimitive(this, (nuint)value) :
                    throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType),
            };
        }
 
        protected override bool IsByRefImpl() => RuntimeTypeHandle.IsByRef(this);
 
        protected override bool IsPrimitiveImpl() => RuntimeTypeHandle.IsPrimitive(this);
 
        protected override bool IsPointerImpl() => RuntimeTypeHandle.IsPointer(this);
 
        protected override bool IsCOMObjectImpl() => RuntimeTypeHandle.IsComObject(this, false);
 
        public override bool IsInstanceOfType([NotNullWhen(true)] object? o) => RuntimeTypeHandle.IsInstanceOfType(this, o);
 
        public override bool IsAssignableFrom([NotNullWhen(true)] TypeInfo? typeInfo)
            => typeInfo != null && IsAssignableFrom(typeInfo.AsType());
 
        public override bool IsAssignableFrom([NotNullWhen(true)] Type? c)
        {
            if (c is null)
                return false;
 
            if (ReferenceEquals(c, this))
                return true;
 
            // For runtime type, let the VM decide.
            if (c.UnderlyingSystemType is RuntimeType fromType)
            {
                // both this and c (or their underlying system types) are runtime types
                return RuntimeTypeHandle.CanCastTo(fromType, this);
            }
 
            // Special case for TypeBuilder to be backward-compatible.
            if (c is Reflection.Emit.TypeBuilder)
            {
                // If c is a subclass of this class, then c can be cast to this type.
                if (c.IsSubclassOf(this))
                    return true;
 
                if (IsInterface)
                {
                    return c.ImplementInterface(this);
                }
                else if (IsGenericParameter)
                {
                    Type[] constraints = GetGenericParameterConstraints();
                    for (int i = 0; i < constraints.Length; i++)
                        if (!constraints[i].IsAssignableFrom(c))
                            return false;
 
                    return true;
                }
            }
 
            // For anything else we return false.
            return false;
        }
 
        [DebuggerStepThrough]
        [DebuggerHidden]
        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
        public override object? InvokeMember(
            string name, BindingFlags bindingFlags, Binder? binder, object? target,
            object?[]? providedArgs, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParams)
        {
            const BindingFlags MemberBindingMask = (BindingFlags)0x000000FF;
            const BindingFlags InvocationMask = (BindingFlags)0x0000FF00;
            const BindingFlags BinderGetSetField = BindingFlags.GetField | BindingFlags.SetField;
            const BindingFlags BinderGetSetProperty = BindingFlags.GetProperty | BindingFlags.SetProperty;
            const BindingFlags BinderNonCreateInstance = BindingFlags.InvokeMethod | BinderGetSetField | BinderGetSetProperty;
            const BindingFlags BinderNonFieldGetSet = (BindingFlags)0x00FFF300;
 
            if (IsGenericParameter)
                throw new InvalidOperationException(SR.Arg_GenericParameter);
 
            if ((bindingFlags & InvocationMask) == 0)
                // "Must specify binding flags describing the invoke operation required."
                throw new ArgumentException(SR.Arg_NoAccessSpec, nameof(bindingFlags));
 
            // Provide a default binding mask if none is provided
            if ((bindingFlags & MemberBindingMask) == 0)
            {
                bindingFlags |= BindingFlags.Instance | BindingFlags.Public;
 
                if ((bindingFlags & BindingFlags.CreateInstance) == 0)
                    bindingFlags |= BindingFlags.Static;
            }
 
            // There must not be more named parameters than provided arguments
            if (namedParams != null)
            {
                if (providedArgs != null)
                {
                    if (namedParams.Length > providedArgs.Length)
                        throw new ArgumentException(SR.Arg_NamedParamTooBig, nameof(namedParams));
                }
                else
                {
                    if (namedParams.Length != 0)
                        throw new ArgumentException(SR.Arg_NamedParamTooBig, nameof(namedParams));
                }
            }
 
#if FEATURE_COMINTEROP
            if (target != null && target.GetType().IsCOMObject)
            {
                const BindingFlags ClassicBindingMask =
                    BindingFlags.InvokeMethod | BindingFlags.GetProperty | BindingFlags.SetProperty |
                    BindingFlags.PutDispProperty | BindingFlags.PutRefDispProperty;
 
                if ((bindingFlags & ClassicBindingMask) == 0)
                    throw new ArgumentException(SR.Arg_COMAccess, nameof(bindingFlags));
 
                if ((bindingFlags & BindingFlags.GetProperty) != 0 && (bindingFlags & ClassicBindingMask & ~(BindingFlags.GetProperty | BindingFlags.InvokeMethod)) != 0)
                    throw new ArgumentException(SR.Arg_PropSetGet, nameof(bindingFlags));
 
                if ((bindingFlags & BindingFlags.InvokeMethod) != 0 && (bindingFlags & ClassicBindingMask & ~(BindingFlags.GetProperty | BindingFlags.InvokeMethod)) != 0)
                    throw new ArgumentException(SR.Arg_PropSetInvoke, nameof(bindingFlags));
 
                if ((bindingFlags & BindingFlags.SetProperty) != 0 && (bindingFlags & ClassicBindingMask & ~BindingFlags.SetProperty) != 0)
                    throw new ArgumentException(SR.Arg_COMPropSetPut, nameof(bindingFlags));
 
                if ((bindingFlags & BindingFlags.PutDispProperty) != 0 && (bindingFlags & ClassicBindingMask & ~BindingFlags.PutDispProperty) != 0)
                    throw new ArgumentException(SR.Arg_COMPropSetPut, nameof(bindingFlags));
 
                if ((bindingFlags & BindingFlags.PutRefDispProperty) != 0 && (bindingFlags & ClassicBindingMask & ~BindingFlags.PutRefDispProperty) != 0)
                    throw new ArgumentException(SR.Arg_COMPropSetPut, nameof(bindingFlags));
 
                ArgumentNullException.ThrowIfNull(name);
 
                bool[]? isByRef = modifiers?[0].IsByRefArray;
 
                // pass LCID_ENGLISH_US if no explicit culture is specified to match the behavior of VB
                int lcid = (culture == null ? 0x0409 : culture.LCID);
 
                // If a request to not wrap exceptions was made, we will unwrap
                // the TargetInvocationException since that is what will be thrown.
                bool unwrapExceptions = (bindingFlags & BindingFlags.DoNotWrapExceptions) != 0;
                try
                {
                    return InvokeDispMethod(name, bindingFlags, target, providedArgs, isByRef, lcid, namedParams);
                }
                catch (TargetInvocationException e) when (unwrapExceptions)
                {
                    // For target invocation exceptions, we need to unwrap the inner exception and
                    // re-throw it.
                    throw e.InnerException!;
                }
            }
#endif // FEATURE_COMINTEROP
 
            if (namedParams != null && Array.IndexOf(namedParams, null!) >= 0)
                throw new ArgumentException(SR.Arg_NamedParamNull, nameof(namedParams));
 
            int argCnt = (providedArgs != null) ? providedArgs.Length : 0;
 
            binder ??= DefaultBinder;
 
            // Delegate to Activator.CreateInstance
            if ((bindingFlags & BindingFlags.CreateInstance) != 0)
            {
                if ((bindingFlags & BindingFlags.CreateInstance) != 0 && (bindingFlags & BinderNonCreateInstance) != 0)
                    // "Can not specify both CreateInstance and another access type."
                    throw new ArgumentException(SR.Arg_CreatInstAccess, nameof(bindingFlags));
 
                return Activator.CreateInstance(this, bindingFlags, binder, providedArgs, culture);
            }
 
            // PutDispProperty and\or PutRefDispProperty ==> SetProperty.
            if ((bindingFlags & (BindingFlags.PutDispProperty | BindingFlags.PutRefDispProperty)) != 0)
                bindingFlags |= BindingFlags.SetProperty;
 
            ArgumentNullException.ThrowIfNull(name);
            if (name.Length == 0 || name.Equals("[DISPID=0]"))
            {
                // in InvokeMember we always pretend there is a default member if none is provided and we make it ToString
                name = GetDefaultMemberName() ?? "ToString";
            }
 
            // GetField or SetField
            bool IsGetField = (bindingFlags & BindingFlags.GetField) != 0;
            bool IsSetField = (bindingFlags & BindingFlags.SetField) != 0;
 
            if (IsGetField || IsSetField)
            {
                if (IsGetField)
                {
                    if (IsSetField)
                        throw new ArgumentException(SR.Arg_FldSetGet, nameof(bindingFlags));
 
                    if ((bindingFlags & BindingFlags.SetProperty) != 0)
                        throw new ArgumentException(SR.Arg_FldGetPropSet, nameof(bindingFlags));
                }
                else
                {
                    Debug.Assert(IsSetField);
 
                    ArgumentNullException.ThrowIfNull(providedArgs);
 
                    if ((bindingFlags & BindingFlags.GetProperty) != 0)
                        throw new ArgumentException(SR.Arg_FldSetPropGet, nameof(bindingFlags));
 
                    if ((bindingFlags & BindingFlags.InvokeMethod) != 0)
                        throw new ArgumentException(SR.Arg_FldSetInvoke, nameof(bindingFlags));
                }
 
                // Lookup Field
                FieldInfo? selFld = null;
                FieldInfo[]? flds = GetMember(name, MemberTypes.Field, bindingFlags) as FieldInfo[];
 
                Debug.Assert(flds != null);
 
                if (flds.Length == 1)
                {
                    selFld = flds[0];
                }
                else if (flds.Length > 0)
                {
                    selFld = binder.BindToField(bindingFlags, flds, IsGetField ? Empty.Value : providedArgs![0]!, culture);
                }
 
                if (selFld != null)
                {
                    // Invocation on a field
                    if (selFld.FieldType.IsArray || ReferenceEquals(selFld.FieldType, typeof(Array)))
                    {
                        // Invocation of an array Field
                        int idxCnt;
                        if ((bindingFlags & BindingFlags.GetField) != 0)
                        {
                            idxCnt = argCnt;
                        }
                        else
                        {
                            idxCnt = argCnt - 1;
                        }
 
                        if (idxCnt > 0)
                        {
                            // Verify that all of the index values are ints
                            int[] idx = new int[idxCnt];
                            for (int i = 0; i < idxCnt; i++)
                            {
                                try
                                {
                                    idx[i] = ((IConvertible)providedArgs![i]!).ToInt32(null);
                                }
                                catch (InvalidCastException)
                                {
                                    throw new ArgumentException(SR.Arg_IndexMustBeInt);
                                }
                            }
 
                            // Set or get the value...
                            Array a = (Array)selFld.GetValue(target)!;
 
                            // Set or get the value in the array
                            if ((bindingFlags & BindingFlags.GetField) != 0)
                            {
                                return a.GetValue(idx);
                            }
                            else
                            {
                                a.SetValue(providedArgs![idxCnt], idx);
                                return null;
                            }
                        }
                    }
 
                    if (IsGetField)
                    {
                        if (argCnt != 0)
                            throw new ArgumentException(SR.Arg_FldGetArgErr, nameof(bindingFlags));
 
                        return selFld.GetValue(target);
                    }
                    else
                    {
                        if (argCnt != 1)
                            throw new ArgumentException(SR.Arg_FldSetArgErr, nameof(bindingFlags));
 
                        selFld.SetValue(target, providedArgs![0], bindingFlags, binder, culture);
                        return null;
                    }
                }
 
                if ((bindingFlags & BinderNonFieldGetSet) == 0)
                    throw new MissingFieldException(FullName, name);
            }
 
            // @Legacy - This is RTM behavior
            bool isGetProperty = (bindingFlags & BindingFlags.GetProperty) != 0;
            bool isSetProperty = (bindingFlags & BindingFlags.SetProperty) != 0;
 
            if (isGetProperty || isSetProperty)
            {
                if (isGetProperty)
                {
                    Debug.Assert(!IsSetField);
 
                    if (isSetProperty)
                        throw new ArgumentException(SR.Arg_PropSetGet, nameof(bindingFlags));
                }
                else
                {
                    Debug.Assert(isSetProperty);
 
                    Debug.Assert(!IsGetField);
 
                    if ((bindingFlags & BindingFlags.InvokeMethod) != 0)
                        throw new ArgumentException(SR.Arg_PropSetInvoke, nameof(bindingFlags));
                }
            }
 
            MethodInfo[]? finalists = null;
            MethodInfo? finalist = null;
 
            if ((bindingFlags & BindingFlags.InvokeMethod) != 0)
            {
                // Lookup Methods
                MethodInfo[] semiFinalists = (GetMember(name, MemberTypes.Method, bindingFlags) as MethodInfo[])!;
                List<MethodInfo>? results = null;
 
                for (int i = 0; i < semiFinalists.Length; i++)
                {
                    MethodInfo semiFinalist = semiFinalists[i];
                    Debug.Assert(semiFinalist != null);
 
                    if (!FilterApplyMethodInfo((RuntimeMethodInfo)semiFinalist, bindingFlags, CallingConventions.Any, new Type[argCnt]))
                        continue;
 
                    if (finalist == null)
                    {
                        finalist = semiFinalist;
                    }
                    else
                    {
                        results ??= new List<MethodInfo>(semiFinalists.Length) { finalist };
                        results.Add(semiFinalist);
                    }
                }
 
                if (results != null)
                {
                    Debug.Assert(results.Count > 1);
                    finalists = results.ToArray();
                }
            }
 
            Debug.Assert(finalists == null || finalist != null);
 
            // BindingFlags.GetProperty or BindingFlags.SetProperty
            if (finalist == null && isGetProperty || isSetProperty)
            {
                // Lookup Property
                PropertyInfo[] semiFinalists = (GetMember(name, MemberTypes.Property, bindingFlags) as PropertyInfo[])!;
                List<MethodInfo>? results = null;
 
                for (int i = 0; i < semiFinalists.Length; i++)
                {
                    MethodInfo? semiFinalist = null;
 
                    if (isSetProperty)
                    {
                        semiFinalist = semiFinalists[i].GetSetMethod(true);
                    }
                    else
                    {
                        semiFinalist = semiFinalists[i].GetGetMethod(true);
                    }
 
                    if (semiFinalist == null)
                        continue;
 
                    if (!FilterApplyMethodInfo((RuntimeMethodInfo)semiFinalist, bindingFlags, CallingConventions.Any, new Type[argCnt]))
                        continue;
 
                    if (finalist == null)
                    {
                        finalist = semiFinalist;
                    }
                    else
                    {
                        results ??= new List<MethodInfo>(semiFinalists.Length) { finalist };
                        results.Add(semiFinalist);
                    }
                }
 
                if (results != null)
                {
                    Debug.Assert(results.Count > 1);
                    finalists = results.ToArray();
                }
            }
 
            if (finalist != null)
            {
                // Invoke
                if (finalists == null &&
                    argCnt == 0 &&
                    finalist.GetParametersAsSpan().Length == 0 &&
                    (bindingFlags & BindingFlags.OptionalParamBinding) == 0)
                {
                    return finalist.Invoke(target, bindingFlags, binder, providedArgs, culture);
                }
 
                finalists ??= new MethodInfo[] { finalist };
                providedArgs ??= Array.Empty<object>();
                object? state = null;
                MethodBase? invokeMethod = null;
 
                try { invokeMethod = binder.BindToMethod(bindingFlags, finalists, ref providedArgs!, modifiers, culture, namedParams, out state); }
                catch (MissingMethodException) { }
 
                if (invokeMethod == null)
                    throw new MissingMethodException(FullName, name);
 
                object? result = ((MethodInfo)invokeMethod).Invoke(target, bindingFlags, binder, providedArgs, culture);
 
                if (state != null)
                    binder.ReorderArgumentArray(ref providedArgs, state);
 
                return result;
            }
 
            throw new MissingMethodException(FullName, name);
        }
 
        private RuntimeType? GetBaseType()
        {
            if (IsInterface)
                return null;
 
            if (RuntimeTypeHandle.IsGenericVariable(this))
            {
                Type[] constraints = GetGenericParameterConstraints();
 
                RuntimeType baseType = ObjectType;
 
                for (int i = 0; i < constraints.Length; i++)
                {
                    RuntimeType constraint = (RuntimeType)constraints[i];
 
                    if (constraint.IsInterface)
                        continue;
 
                    if (constraint.IsGenericParameter)
                    {
                        GenericParameterAttributes special = constraint.GenericParameterAttributes;
 
                        if ((special & GenericParameterAttributes.ReferenceTypeConstraint) == 0 &&
                            (special & GenericParameterAttributes.NotNullableValueTypeConstraint) == 0)
                            continue;
                    }
 
                    baseType = constraint;
                }
 
                if (baseType == ObjectType)
                {
                    GenericParameterAttributes special = GenericParameterAttributes;
                    if ((special & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
                        baseType = ValueType;
                }
 
                return baseType;
            }
 
            return RuntimeTypeHandle.GetBaseType(this);
        }
 
        private static void ThrowIfTypeNeverValidGenericArgument(RuntimeType type)
        {
            if (type.IsPointer || type.IsFunctionPointer || type.IsByRef || type == typeof(void))
                throw new ArgumentException(
                    SR.Format(SR.Argument_NeverValidGenericArgument, type));
        }
 
        internal static void SanityCheckGenericArguments(RuntimeType[] genericArguments, RuntimeType[] genericParameters)
        {
            ArgumentNullException.ThrowIfNull(genericArguments);
 
            for (int i = 0; i < genericArguments.Length; i++)
            {
                ArgumentNullException.ThrowIfNull(genericArguments[i], null);
                ThrowIfTypeNeverValidGenericArgument(genericArguments[i]);
            }
 
            if (genericArguments.Length != genericParameters.Length)
                throw new ArgumentException(
                    SR.Format(SR.Argument_NotEnoughGenArguments, genericArguments.Length, genericParameters.Length));
        }
 
        internal static CorElementType GetUnderlyingType(RuntimeType type)
        {
            if (type.IsActualEnum)
            {
                type = (RuntimeType)Enum.GetUnderlyingType(type);
            }
 
            return RuntimeTypeHandle.GetCorElementType(type);
        }
 
 
        // AggressiveInlining used since on hot path for reflection.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static bool TryGetByRefElementType(RuntimeType type, [NotNullWhen(true)] out RuntimeType? elementType)
        {
            CorElementType corElemType = RuntimeTypeHandle.GetCorElementType(type);
            if (corElemType == CorElementType.ELEMENT_TYPE_BYREF)
            {
                elementType = RuntimeTypeHandle.GetElementType(type);
                return true;
            }
 
            elementType = null;
            return false;
        }
 
        private enum CheckValueStatus
        {
            Success = 0,
            ArgumentException,
            NotSupported_ByRefLike
        }
 
        /// <summary>
        /// Verify <paramref name="value"/> and optionally convert the value for special cases.
        /// </summary>
        /// <returns>True if the value requires a copy-back to the original object[].</returns>
        internal bool CheckValue(ref object? value)
        {
            Debug.Assert(!IsGenericParameter);
 
            // Fast path to whether a value can be assigned without conversion.
            if (IsInstanceOfType(value))
            {
                if (IsNullableOfT)
                {
                    // Pass as a true boxed Nullable<T>, not as a T or null.
                    value = RuntimeMethodHandle.ReboxToNullable(value, this);
                    return true;
                }
 
                return false;
            }
 
            bool copyBack = false;
            CheckValueStatus result = TryChangeType(ref value, ref copyBack);
            if (result == CheckValueStatus.Success)
            {
                return copyBack;
            }
 
            switch (result)
            {
                case CheckValueStatus.ArgumentException:
                    throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value?.GetType(), this));
                case CheckValueStatus.NotSupported_ByRefLike:
                    throw new NotSupportedException(SR.NotSupported_ByRefLike);
            }
 
            Debug.Fail("Error result not expected");
            return false;
        }
 
        /// <summary>
        /// Verify <paramref name="value"/> and optionally convert the value for special cases.
        /// </summary>
        /// <returns>True if the value requires a copy-back to the original object[] or Span{object}.</returns>
        internal bool CheckValue(
            ref object? value,
            Binder? binder,
            CultureInfo? culture,
            BindingFlags invokeAttr)
        {
            Debug.Assert(!IsGenericParameter);
 
            // Fast path to whether a value can be assigned without conversion.
            if (IsInstanceOfType(value))
            {
                if (IsNullableOfT)
                {
                    // Pass as a true boxed Nullable<T>, not as a T or null.
                    value = RuntimeMethodHandle.ReboxToNullable(value, this);
                    return true;
                }
 
                return false;
            }
 
            bool copyBack = false;
            CheckValueStatus result = TryChangeType(ref value, ref copyBack);
            if (result == CheckValueStatus.Success)
            {
                return copyBack;
            }
 
            if (result == CheckValueStatus.ArgumentException && (invokeAttr & BindingFlags.ExactBinding) == 0)
            {
                Debug.Assert(value != null);
 
                // Use the binder
                if (binder != null && binder != DefaultBinder)
                {
                    value = binder.ChangeType(value, this, culture);
                    if (IsInstanceOfType(value))
                    {
                        if (IsNullableOfT)
                        {
                            // Pass as a true boxed Nullable<T>, not as a T or null.
                            value = RuntimeMethodHandle.ReboxToNullable(value, this);
                        }
 
                        return true;
                    }
 
                    result = TryChangeType(ref value, ref copyBack);
                    if (result == CheckValueStatus.Success)
                    {
                        return copyBack;
                    }
                }
            }
 
            switch (result)
            {
                case CheckValueStatus.ArgumentException:
                    throw new ArgumentException(SR.Format(SR.Arg_ObjObjEx, value?.GetType(), this));
                case CheckValueStatus.NotSupported_ByRefLike:
                    throw new NotSupportedException(SR.NotSupported_ByRefLike);
            }
 
            Debug.Fail("Error result not expected");
            return false;
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
            Justification = "AllocateValueType is only called on a ValueType. You can always create an instance of a ValueType.")]
        [return: NotNullIfNotNull(nameof(value))]
        internal static object? AllocateValueType(RuntimeType type, object? value)
        {
            Debug.Assert(type.IsValueType);
            Debug.Assert(!type.IsByRefLike);
 
            if (value is not null)
            {
                // Make a copy of the provided value by re-boxing the existing value's underlying data.
                Debug.Assert(type.IsEquivalentTo(value.GetType()));
                return RuntimeHelpers.Box(ref RuntimeHelpers.GetRawData(value), type.TypeHandle)!;
            }
 
            if (type.IsNullableOfT)
            {
                // If the type is Nullable<T>, then create a true boxed Nullable<T> of the default Nullable<T> value.
                return RuntimeMethodHandle.ReboxToNullable(null, type);
            }
 
            // Otherwise, just create a default instance of the type.
            return RuntimeHelpers.GetUninitializedObject(type);
        }
 
        private CheckValueStatus TryChangeType(ref object? value, ref bool copyBack)
        {
            RuntimeType? sigElementType;
            if (TryGetByRefElementType(this, out sigElementType))
            {
                Debug.Assert(!sigElementType.IsGenericParameter);
                copyBack = true;
 
                if (sigElementType.IsInstanceOfType(value))
                {
                    if (sigElementType.IsActualValueType)
                    {
                        if (sigElementType.IsNullableOfT)
                        {
                            // Pass as a true boxed Nullable<T>, not as a T or null.
                            value = RuntimeMethodHandle.ReboxToNullable(value, sigElementType);
                        }
                        else
                        {
                            // Make a copy to prevent the boxed instance from being directly modified by the method.
                            value = AllocateValueType(sigElementType, value);
                        }
                    }
 
                    return CheckValueStatus.Success;
                }
 
                if (value == null)
                {
                    if (!sigElementType.IsActualValueType)
                    {
                        return CheckValueStatus.Success;
                    }
 
                    if (sigElementType.IsByRefLike)
                    {
                        return CheckValueStatus.NotSupported_ByRefLike;
                    }
 
                    // Allocate default<T>.
                    value = AllocateValueType(sigElementType, value: null);
                    return CheckValueStatus.Success;
                }
 
                return CheckValueStatus.ArgumentException;
            }
 
            if (value == null)
            {
                if (IsPointer || IsFunctionPointer)
                {
                    // Pass an IntPtr instead of null.
                    value = default(IntPtr);
                    return CheckValueStatus.Success;
                }
 
                if (!IsActualValueType)
                {
                    return CheckValueStatus.Success;
                }
 
                if (IsByRefLike)
                {
                    return CheckValueStatus.NotSupported_ByRefLike;
                }
 
                // Allocate default<T>.
                value = AllocateValueType(this, value: null);
                return CheckValueStatus.Success;
            }
 
            // Check the strange ones courtesy of reflection:
            // - Implicit cast between primitives
            // - Enum treated as underlying type
            // - Pointer (*) types to IntPtr (if dest is IntPtr)
            // - System.Reflection.Pointer to appropriate pointer (*) type (if dest is pointer type)
            if (IsPointer || IsEnum || IsPrimitive || IsFunctionPointer)
                return TryChangeTypeSpecial(ref value);
 
            return CheckValueStatus.ArgumentException;
        }
    }
}