File: System\Reflection\Runtime\TypeInfos\RuntimeTypeInfo.cs
Web Access
Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\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.Reflection;
using System.Reflection.Runtime.General;
using System.Reflection.Runtime.MethodInfos;
using System.Runtime.CompilerServices;

using Internal.Reflection.Augments;
using Internal.Reflection.Core.Execution;
using Internal.Runtime.Augments;

using StructLayoutAttribute = System.Runtime.InteropServices.StructLayoutAttribute;
using System.Threading;

namespace System.Reflection.Runtime.TypeInfos
{
    //
    // Abstract base class for all TypeInfo's implemented by the runtime.
    //
    // This base class performs several services:
    //
    //   - Provides default implementations whenever possible. Some of these
    //     return the "common" error result for narrowly applicable properties (such as those
    //     that apply only to generic parameters.)
    //
    //   - Inverts the DeclaredMembers/DeclaredX relationship (DeclaredMembers is auto-implemented, others
    //     are overridden as abstract. This ordering makes more sense when reading from metadata.)
    //
    //   - Overrides many "NotImplemented" members in TypeInfo with abstracts so failure to implement
    //     shows up as build error.
    //
    [DebuggerDisplay("{_debugName}")]
    internal abstract partial class RuntimeTypeInfo
    {
        protected RuntimeTypeInfo()
        {
        }

        public virtual bool IsTypeDefinition => false;
        public virtual bool IsGenericTypeDefinition => false;
        public virtual bool HasElementType => false;
        public virtual bool IsArray => false;
        public virtual bool IsSZArray => false;
        public virtual bool IsVariableBoundArray => false;
        public virtual bool IsByRef => false;
        public virtual bool IsPointer => false;
        public virtual bool IsGenericParameter => false;
        public virtual bool IsGenericTypeParameter => false;
        public virtual bool IsGenericMethodParameter => false;
        public virtual bool IsConstructedGenericType => false;
        public virtual bool IsByRefLike => false;

        public bool IsGenericType => IsGenericTypeDefinition || IsConstructedGenericType;

        public bool IsVoid => InternalTypeHandleIfAvailable.Equals(typeof(void).TypeHandle);

        public abstract string Name { get; }

        public abstract Assembly Assembly { get; }

        public string AssemblyQualifiedName
        {
            get
            {
                string fullName = FullName;
                if (fullName == null)   // Some Types (such as generic parameters) return null for FullName by design.
                    return null;
                string assemblyName = InternalFullNameOfAssembly;
                return fullName + ", " + assemblyName;
            }
        }

        public Type? BaseType
        {
            get
            {
                // If this has a RuntimeTypeHandle, let the underlying runtime engine have the first crack. If it refuses, fall back to metadata.
                RuntimeTypeHandle typeHandle = InternalTypeHandleIfAvailable;
                if (!typeHandle.IsNull())
                {
                    RuntimeTypeHandle baseTypeHandle;
                    if (RuntimeAugments.TryGetBaseType(typeHandle, out baseTypeHandle))
                        return Type.GetTypeFromHandle(baseTypeHandle);
                }

                Type baseType = BaseTypeWithoutTheGenericParameterQuirk;
                if (baseType != null && baseType.IsGenericParameter)
                {
                    // Desktop quirk: a generic parameter whose constraint is another generic parameter reports its BaseType as System.Object
                    // unless that other generic parameter has a "class" constraint.
                    GenericParameterAttributes genericParameterAttributes = baseType.GenericParameterAttributes;
                    if (0 == (genericParameterAttributes & GenericParameterAttributes.ReferenceTypeConstraint))
                        baseType = typeof(object);
                }
                return baseType;
            }
        }

        public abstract bool ContainsGenericParameters { get; }

        public abstract IEnumerable<CustomAttributeData> CustomAttributes { get; }

        //
        // Left unsealed as generic parameter types must override.
        //
        public virtual MethodBase DeclaringMethod
        {
            get
            {
                Debug.Assert(!IsGenericParameter);
                throw new InvalidOperationException(SR.Arg_NotGenericParameter);
            }
        }

        //
        // Equals()/GetHashCode()
        //
        // RuntimeTypeInfo objects are interned to preserve the app-compat rule that Type objects (which are the same as TypeInfo objects)
        // can be compared using reference equality.
        //
        // We use weak pointers to intern the objects. This means we can use instance equality to implement Equals() but we cannot use
        // the instance hashcode to implement GetHashCode() (otherwise, the hash code will not be stable if the TypeInfo is released and recreated.)
        // Thus, we override and seal Equals() here but defer to a flavor-specific hash code implementation.
        //
        public override bool Equals(object obj)
        {
            return object.ReferenceEquals(this, obj);
        }

        public bool Equals(Type o)
        {
            return object.ReferenceEquals(this, o);
        }

        public abstract override int GetHashCode();

        public abstract string FullName { get; }

        //
        // Left unsealed as generic parameter types must override.
        //
        public virtual GenericParameterAttributes GenericParameterAttributes
        {
            get
            {
                Debug.Assert(!IsGenericParameter);
                throw new InvalidOperationException(SR.Arg_NotGenericParameter);
            }
        }

        //
        // Left unsealed as generic parameter types must override this.
        //
        public virtual int GenericParameterPosition
        {
            get
            {
                Debug.Assert(!IsGenericParameter);
                throw new InvalidOperationException(SR.Arg_NotGenericParameter);
            }
        }

        public Type[] GenericTypeArguments
        {
            get
            {
                return InternalRuntimeGenericTypeArguments.ToTypeArray();
            }
        }

        public MemberInfo[] GetDefaultMembers()
        {
            string? defaultMemberName = GetDefaultMemberName();
            return defaultMemberName != null ? GetMember(defaultMemberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public) : Array.Empty<MemberInfo>();
        }

        public InterfaceMapping GetInterfaceMap([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type interfaceType)
        {
            // restrictions and known limitations compared to CoreCLR:
            // - only interface.GetMethods() reflection visible interface methods are returned
            // - all visible members of the interface must be reflection invokeable
            // - this type and ifaceType must not be an open generic type
            // - if this type and the method implementing the interface method are abstract, an exception is thrown

            if (IsGenericParameter)
                throw new InvalidOperationException(SR.Arg_GenericParameter);

            ArgumentNullException.ThrowIfNull(interfaceType);

            if (!(interfaceType is RuntimeType))
                throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(interfaceType));

            RuntimeTypeHandle typeHandle = TypeHandle;
            RuntimeTypeHandle interfaceTypeHandle = interfaceType.TypeHandle;

            if (RuntimeAugments.IsInterface(typeHandle))
                throw new ArgumentException(SR.Argument_InterfaceMap);

            if (!RuntimeAugments.IsInterface(interfaceTypeHandle))
                throw new ArgumentException(SR.Arg_MustBeInterface);

            if (!RuntimeAugments.IsAssignableFrom(interfaceTypeHandle, typeHandle))
                throw new ArgumentException(SR.Arg_NotFoundIFace);

            // SZArrays implement the methods on IList`1, IEnumerable`1, and ICollection`1 with
            // runtime magic. We don't have accurate interface maps for them.
            if (IsSZArray && interfaceType.IsGenericType)
                throw new ArgumentException(SR.Argument_ArrayGetInterfaceMap);

            ReflectionCoreExecution.ExecutionEnvironment.GetInterfaceMap(this.ToType(), interfaceType, out MethodInfo[] interfaceMethods, out MethodInfo[] targetMethods);

            InterfaceMapping im;
            im.InterfaceType = interfaceType;
            im.TargetType = this.ToType();
            im.InterfaceMethods = interfaceMethods;
            im.TargetMethods = targetMethods;

            return im;
        }

        //
        // Implements the correct GUID behavior for all "constructed" types (i.e. returning an all-zero GUID.) Left unsealed
        // so that RuntimeNamedTypeInfo can override.
        //
        public virtual Guid GUID
        {
            get
            {
                return Guid.Empty;
            }
        }

        public virtual bool IsFunctionPointer => false;
        public virtual bool IsUnmanagedFunctionPointer => false;

        public virtual Type[] GetFunctionPointerCallingConventions()
        {
            throw new InvalidOperationException(SR.InvalidOperation_NotFunctionPointer);
        }

        public virtual Type[] GetFunctionPointerParameterTypes()
        {
            throw new InvalidOperationException(SR.InvalidOperation_NotFunctionPointer);
        }

        public virtual Type GetFunctionPointerReturnType()
        {
            throw new InvalidOperationException(SR.InvalidOperation_NotFunctionPointer);
        }

        public abstract bool HasSameMetadataDefinitionAs(MemberInfo other);

        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = "Interface lists on base types will be preserved same as for the current type")]
        public Type[] GetInterfaces()
        {
            // If this has a RuntimeTypeHandle, let the underlying runtime engine have the first crack. If it refuses, fall back to metadata.
            RuntimeTypeHandle typeHandle = InternalTypeHandleIfAvailable;
            if (!typeHandle.IsNull() && !IsGenericTypeDefinition)
                return ToType().GetInterfaces();

            ArrayBuilder<Type> result = default;

            TypeContext typeContext = this.TypeContext;
            Type baseType = this.BaseTypeWithoutTheGenericParameterQuirk;
            if (baseType != null)
                result.Append(baseType.GetInterfaces());
            foreach (QTypeDefRefOrSpec directlyImplementedInterface in this.TypeRefDefOrSpecsForDirectlyImplementedInterfaces)
            {
                Type ifc = directlyImplementedInterface.Resolve(typeContext).ToType();
                if (result.Contains(ifc))
                    continue;
                result.Add(ifc);
                foreach (Type indirectIfc in ifc.GetInterfaces())
                {
                    if (result.Contains(indirectIfc))
                        continue;
                    result.Add(indirectIfc);
                }
            }

            return result.ToArray();
        }

        public bool IsAssignableFrom(Type c)
        {
            if (c == null)
                return false;

            if (object.ReferenceEquals(c, this))
                return true;

            c = c.UnderlyingSystemType;

            Type typeInfo = c;
            RuntimeTypeInfo toTypeInfo = this;

            if (typeInfo is not RuntimeType)
                return false;  // Desktop compat: If typeInfo is null, or implemented by a different Reflection implementation, return "false."

            RuntimeTypeInfo fromTypeInfo = typeInfo.ToRuntimeTypeInfo();

            if (toTypeInfo.Equals(fromTypeInfo))
                return true;

            RuntimeTypeHandle toTypeHandle = toTypeInfo.InternalTypeHandleIfAvailable;
            RuntimeTypeHandle fromTypeHandle = fromTypeInfo.InternalTypeHandleIfAvailable;
            bool haveTypeHandles = !(toTypeHandle.IsNull() || fromTypeHandle.IsNull());
            if (haveTypeHandles)
            {
                // If both types have type handles, let MRT handle this. It's not dependent on metadata.
                if (RuntimeAugments.IsAssignableFrom(toTypeHandle, fromTypeHandle))
                    return true;

                // Runtime IsAssignableFrom does not handle casts from generic type definitions: always returns false. For those, we fall through to the
                // managed implementation. For everyone else, return "false".
                //
                // Runtime IsAssignableFrom does not handle pointer -> UIntPtr cast.
                if (!(fromTypeInfo.IsGenericTypeDefinition || fromTypeInfo.IsPointer))
                    return false;
            }

            // If we got here, the types are open, or reduced away, or otherwise lacking in type handles. Perform the IsAssignability check in managed code.
            return Assignability.IsAssignableFrom(this.ToType(), typeInfo);
        }

        public MemberTypes MemberType
        {
            get
            {
                TypeAttributes attributes = Attributes;

                if ((attributes & TypeAttributes.VisibilityMask) is TypeAttributes.Public or TypeAttributes.NotPublic)
                    return MemberTypes.TypeInfo;
                else
                    return MemberTypes.NestedType;
            }
        }

        //
        // Left unsealed as there are so many subclasses. Need to be overridden by EcmaFormatRuntimeNamedTypeInfo and RuntimeConstructedGenericTypeInfo
        //
        public abstract int MetadataToken
        {
            get;
        }

        public Module Module
        {
            get
            {
                return Assembly.ManifestModule;
            }
        }

        public abstract string Namespace { get; }

        public Type[] GenericTypeParameters
        {
            get
            {
                return RuntimeGenericTypeParameters.ToTypeArray();
            }
        }

        //
        // Left unsealed as array types must override this.
        //
        public virtual int GetArrayRank()
        {
            Debug.Assert(!IsArray);
            throw new ArgumentException(SR.Argument_HasToBeArrayClass);
        }

        public Type GetElementType()
        {
            return InternalRuntimeElementType?.ToType();
        }

        //
        // Left unsealed as generic parameter types must override.
        //
        public virtual Type[] GetGenericParameterConstraints()
        {
            Debug.Assert(!IsGenericParameter);
            throw new InvalidOperationException(SR.Arg_NotGenericParameter);
        }

        //
        // Left unsealed as generic types must override this.
        //
        public virtual Type GetGenericTypeDefinition()
        {
            Debug.Assert(!IsGenericTypeDefinition && !IsConstructedGenericType);
            throw new InvalidOperationException(SR.InvalidOperation_NotGenericType);
        }

        public virtual Type? GetNullableUnderlyingType() => null;

        internal virtual void GetEnumValuesAndNames(out string[] unsortedNames, out object[] unsortedValues, out bool isFlags)
        {
            throw new NotSupportedException();
        }

        public Type MakeArrayType()
        {
            // Do not implement this as a call to MakeArrayType(1) - they are not interchangeable. MakeArrayType() returns a
            // vector type ("SZArray") while MakeArrayType(1) returns a multidim array of rank 1. These are distinct types
            // in the ECMA model and in CLR Reflection.
            return this.GetArrayType().ToType();
        }

        public Type MakeArrayType(int rank)
        {
            if (rank <= 0)
                throw new IndexOutOfRangeException();
            return this.GetMultiDimArrayType(rank).ToType();
        }

        public Type MakeFunctionPointerType(Type[]? parameterTypes, bool isUnmanaged = false)
        {
            if (this.IsGenericTypeDefinition)
                throw new InvalidOperationException(SR.Format(SR.FunctionPointer_ReturnTypeInvalid, this));

            parameterTypes ??= [];
            RuntimeTypeInfo[] runtimeParameterTypes = new RuntimeTypeInfo[parameterTypes.Length];

            for (int i = 0; i < parameterTypes.Length; i++)
            {
                Type? paramType = parameterTypes[i];
                ArgumentNullException.ThrowIfNull(paramType, nameof(parameterTypes));

                if (paramType is not RuntimeType rtType)
                    return Type.MakeFunctionPointerSignatureType(this.ToType(), parameterTypes, isUnmanaged);

                if (rtType == typeof(void) || rtType.IsGenericTypeDefinition)
                    throw new ArgumentException(SR.Format(SR.FunctionPointer_ParameterInvalid, rtType), nameof(parameterTypes));

                runtimeParameterTypes[i] = rtType.GetRuntimeTypeInfo();
            }

            return this.GetFunctionPointerType(runtimeParameterTypes, isUnmanaged).ToType();
        }

        public Type MakePointerType()
        {
            return this.GetPointerType().ToType();
        }

        public Type MakeByRefType()
        {
            return this.GetByRefType().ToType();
        }

        public Type MakeGenericType(Type[] typeArguments)
        {
            ArgumentNullException.ThrowIfNull(typeArguments);

            if (!IsGenericTypeDefinition)
                throw new InvalidOperationException(SR.Format(SR.Arg_NotGenericTypeDefinition, this));

            // We intentionally don't validate the number of arguments or their suitability to the generic type's constraints.
            // In a pay-for-play world, this can cause needless missing metadata exceptions. There is no harm in creating
            // the Type object for an inconsistent generic type - no MethodTable will ever match it so any attempt to "invoke" it
            // will throw an exception.
            bool foundSignatureType = false;
            RuntimeTypeInfo?[] runtimeTypeArguments = new RuntimeTypeInfo[typeArguments.Length];
            for (int i = 0; i < typeArguments.Length; i++)
            {
                Type typeArgument = typeArguments[i];
                if (typeArgument == null)
                    throw new ArgumentNullException();

                if (typeArgument is RuntimeType typeArgumentAsRuntimeType)
                {
                    runtimeTypeArguments[i] = typeArgumentAsRuntimeType.GetRuntimeTypeInfo();
                }
                else
                {
                    if (typeArgument.IsSignatureType)
                    {
                        foundSignatureType = true;
                    }
                    else
                    {
                        throw new PlatformNotSupportedException(SR.Format(SR.Reflection_CustomReflectionObjectsNotSupported, typeArguments[i]));
                    }
                }
            }

            if (foundSignatureType)
                return new SignatureConstructedGenericType(this.ToType(), typeArguments);

            for (int i = 0; i < typeArguments.Length; i++)
            {
                RuntimeTypeInfo runtimeTypeArgument = runtimeTypeArguments[i]!;

                // Desktop compatibility: Treat generic type definitions as a constructed generic type using the generic parameters as type arguments.
                if (runtimeTypeArgument.IsGenericTypeDefinition)
                    runtimeTypeArgument = runtimeTypeArguments[i] = runtimeTypeArgument.GetConstructedGenericTypeNoConstraintCheck(runtimeTypeArgument.RuntimeGenericTypeParameters);

                if (runtimeTypeArgument.IsByRefLike)
                    throw new TypeLoadException(SR.CannotUseByRefLikeTypeInInstantiation);
            }

            return this.GetConstructedGenericType(runtimeTypeArguments!).ToType();
        }

        public Type DeclaringType
        {
            get
            {
                return this.InternalDeclaringType?.ToType();
            }
        }

        public abstract StructLayoutAttribute StructLayoutAttribute { get; }

        public abstract override string ToString();

        public RuntimeTypeHandle TypeHandle
        {
            get
            {
                RuntimeTypeHandle typeHandle = InternalTypeHandleIfAvailable;
                if (!typeHandle.IsNull())
                    return typeHandle;

                // If a type doesn't have a type handle, it's either because we optimized away the MethodTable
                // but the reflection metadata had to be kept around, or because we have an open type somewhere
                // (open types never get EETypes). Open types are PlatformNotSupported and there's nothing
                // that can be done about that. Missing MethodTable can be fixed by helping the AOT compiler
                // with some hints.
                if (!IsGenericTypeDefinition && ContainsGenericParameters)
                    throw new PlatformNotSupportedException(SR.PlatformNotSupported_NoTypeHandleForOpenTypes);

                // If got here, this is a "plain old type" that has metadata but no type handle. We can get here if the only
                // representation of the type is in the native metadata and there's no MethodTable at the runtime side.
                // If you squint hard, this is a missing metadata situation - the metadata is missing on the runtime side - and
                // the action for the user to take is the same: go mess with RD.XML.
                throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(this.ToType());
            }
        }

        public abstract TypeAttributes Attributes { get; }

        public bool IsAbstract => (Attributes & TypeAttributes.Abstract) != 0;

        public bool IsInterface => (Attributes & TypeAttributes.Interface) != 0;

        public bool IsPrimitive => (Classification & TypeClassification.IsPrimitive) != 0;

        public bool IsValueType => (Classification & TypeClassification.IsValueType) != 0;

        public bool IsEnum => 0 != (Classification & TypeClassification.IsEnum);

        public bool IsActualValueType => IsValueType && !IsGenericParameter;

        public bool IsActualEnum => IsEnum && !IsGenericParameter;

        //
        // Returns the anchoring typedef that declares the members that this type wants returned by the Declared*** properties.
        // The Declared*** properties will project the anchoring typedef's members by overriding their DeclaringType property with "this"
        // and substituting the value of this.TypeContext into any generic parameters.
        //
        // Default implementation returns null which causes the Declared*** properties to return no members.
        //
        // Note that this does not apply to DeclaredNestedTypes. Nested types and their containers have completely separate generic instantiation environments
        // (despite what C# might lead you to think.) Constructed generic types return the exact same same nested types that its generic type definition does
        // - i.e. their DeclaringTypes refer back to the generic type definition, not the constructed generic type.)
        //
        // Note also that we cannot use this anchoring concept for base types because of generic parameters. Generic parameters return
        // a base class and interface list based on its constraints.
        //
        internal virtual RuntimeNamedTypeInfo AnchoringTypeDefinitionForDeclaredMembers
        {
            get
            {
                return null;
            }
        }

        internal abstract RuntimeTypeInfo InternalDeclaringType { get; }

        //
        // Return the full name of the "defining assembly" for the purpose of computing TypeInfo.AssemblyQualifiedName;
        //
        internal abstract string InternalFullNameOfAssembly { get; }

        //
        // Left unsealed as HasElement types must override this.
        //
        internal virtual RuntimeTypeInfo InternalRuntimeElementType
        {
            get
            {
                Debug.Assert(!HasElementType);
                return null;
            }
        }

        //
        // Left unsealed as constructed generic types must override this.
        //
        internal virtual RuntimeTypeInfo[] InternalRuntimeGenericTypeArguments
        {
            get
            {
                Debug.Assert(!IsConstructedGenericType);
                return Array.Empty<RuntimeTypeInfo>();
            }
        }

        internal abstract RuntimeTypeHandle InternalTypeHandleIfAvailable { get; }

        private RuntimeType _type;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public RuntimeType ToType() => _type ?? InitializeType();

        private RuntimeType InitializeType()
        {
            RuntimeTypeHandle runtimeTypeHandle = InternalTypeHandleIfAvailable;
            if (runtimeTypeHandle.IsNull)
            {
                RuntimeType type = new RuntimeType(this);
                if (Interlocked.CompareExchange(ref _type, type, null) != null)
                    type.Free();
            }
            else
            {
                _type = (RuntimeType)Type.GetTypeFromHandle(runtimeTypeHandle)!;
            }
            return _type;
        }

        internal bool IsDelegate
        {
            get
            {
                return 0 != (Classification & TypeClassification.IsDelegate);
            }
        }

        //
        // The non-public version of TypeInfo.GenericTypeParameters (does not array-copy.)
        //
        internal virtual RuntimeTypeInfo[] RuntimeGenericTypeParameters
        {
            get
            {
                Debug.Assert(!(this is RuntimeNamedTypeInfo));
                return Array.Empty<RuntimeTypeInfo>();
            }
        }

        //
        // Normally returns empty: Overridden by array types to return constructors.
        //
        internal virtual IEnumerable<RuntimeConstructorInfo> SyntheticConstructors
        {
            get
            {
                return Array.Empty<RuntimeConstructorInfo>();
            }
        }

        //
        // Normally returns empty: Overridden by array types to return the "Get" and "Set" methods.
        //
        internal virtual IEnumerable<RuntimeMethodInfo> SyntheticMethods
        {
            get
            {
                return Array.Empty<RuntimeMethodInfo>();
            }
        }

        //
        // Returns the base type as a typeDef, Ref, or Spec. Default behavior is to QTypeDefRefOrSpec.Null, which causes BaseType to return null.
        //
        // If you override this method, there is no need to override BaseTypeWithoutTheGenericParameterQuirk.
        //
        internal virtual QTypeDefRefOrSpec TypeRefDefOrSpecForBaseType
        {
            get
            {
                return QTypeDefRefOrSpec.Null;
            }
        }

        //
        // Returns the *directly implemented* interfaces as typedefs, specs or refs. ImplementedInterfaces will take care of the transitive closure and
        // insertion of the TypeContext.
        //
        internal virtual QTypeDefRefOrSpec[] TypeRefDefOrSpecsForDirectlyImplementedInterfaces
        {
            get
            {
                return Array.Empty<QTypeDefRefOrSpec>();
            }
        }

        //
        // Returns the generic parameter substitutions to use when enumerating declared members, base class and implemented interfaces.
        //
        internal virtual TypeContext TypeContext
        {
            get
            {
                return new TypeContext(null, null);
            }
        }

        //
        // Note: This can be (and is) called multiple times. We do not do this work in the constructor as calling ToString()
        // in the constructor causes some serious recursion issues.
        //
        internal RuntimeTypeInfo EstablishDebugName()
        {
#if DEBUG
            if (_debugName == null)
            {
                _debugName = "Constructing..."; // Protect against any inadvertent reentrancy.
                _debugName = ToString() ?? "";
            }
#endif
            return this;
        }

        //
        // This internal method implements BaseType without the following desktop quirk:
        //
        //     class Foo<X,Y>
        //       where X:Y
        //       where Y:MyReferenceClass
        //
        // The desktop reports "X"'s base type as "System.Object" rather than "Y", even though it does
        // report any interfaces that are in MyReferenceClass's interface list.
        //
        // This seriously messes up the implementation of RuntimeTypeInfo.ImplementedInterfaces which assumes
        // that it can recover the transitive interface closure by combining the directly mentioned interfaces and
        // the BaseType's own interface closure.
        //
        // To implement this with the least amount of code smell, we'll implement the idealized version of BaseType here
        // and make the special-case adjustment in the public version of BaseType.
        //
        internal Type BaseTypeWithoutTheGenericParameterQuirk
        {
            get
            {
                QTypeDefRefOrSpec baseTypeDefRefOrSpec = TypeRefDefOrSpecForBaseType;
                RuntimeTypeInfo? baseType = null;
                if (!baseTypeDefRefOrSpec.IsValid)
                {
                    baseType = baseTypeDefRefOrSpec.Resolve(this.TypeContext);
                }
                return baseType?.ToType();
            }
        }

        private string? GetDefaultMemberName()
        {
            Type defaultMemberAttributeType = typeof(DefaultMemberAttribute);
            for (Type type = this.ToType(); type != null; type = type.BaseType!)
            {
                foreach (CustomAttributeData attribute in type.CustomAttributes)
                {
                    if (attribute.AttributeType == defaultMemberAttributeType)
                    {
                        // NOTE: Neither indexing nor cast can fail here. Any attempt to use fewer than 1 argument
                        // or a non-string argument would correctly trigger MissingMethodException before
                        // we reach here as that would be an attempt to reference a non-existent DefaultMemberAttribute
                        // constructor.
                        Debug.Assert(attribute.ConstructorArguments.Count == 1 && attribute.ConstructorArguments[0].Value is string);

                        string? memberName = (string?)(attribute.ConstructorArguments[0].Value);
                        return memberName;
                    }
                }
            }

            return null;
        }

        //
        // Returns a latched set of flags indicating the value of IsValueType, IsEnum, etc.
        //
        private TypeClassification Classification
        {
            get
            {
                // We have a very specialized helper to get the base type.
                // It is not a general purpose base type, but works for the cases we care about.
                // This avoids bringing in full type resolution support including constructing
                // generic types.
                static Type GetLimitedBaseType(RuntimeTypeInfo thisType)
                {
                    // If we have a type handle, just use that
                    RuntimeTypeHandle typeHandle = thisType.InternalTypeHandleIfAvailable;
                    if (!typeHandle.IsNull())
                    {
                        RuntimeTypeHandle baseTypeHandle;
                        if (RuntimeAugments.TryGetBaseType(typeHandle, out baseTypeHandle))
                            return Type.GetTypeFromHandle(baseTypeHandle);
                    }

                    // Metadata fallback. We only care about very limited subset of all possibilities.
                    // The cases that we're interested in will all be definitions, and won't be generic.
                    Type? baseType = null;
                    QTypeDefRefOrSpec baseTypeDefOrRefOrSpec = thisType.TypeRefDefOrSpecForBaseType;
                    if (baseTypeDefOrRefOrSpec.IsTypeDefinition)
                    {
                        QTypeDefinition baseTypeDef = baseTypeDefOrRefOrSpec.ToTypeDefinition();
                        baseType = baseTypeDef.Resolve().ToType();
                    }
                    return baseType;
                }

                static bool IsPrimitiveType(Type type)
                    => type == typeof(bool) || type == typeof(char)
                    || type == typeof(sbyte) || type == typeof(byte)
                    || type == typeof(short) || type == typeof(ushort)
                    || type == typeof(int) || type == typeof(uint)
                    || type == typeof(long) || type == typeof(ulong)
                    || type == typeof(float) || type == typeof(double)
                    || type == typeof(nint) || type == typeof(nuint);

                if (_lazyClassification == 0)
                {
                    TypeClassification classification = TypeClassification.Computed;
                    Type baseType = GetLimitedBaseType(this);
                    if (baseType != null)
                    {
                        Type enumType = typeof(Enum);
                        Type valueType = typeof(ValueType);

                        if (baseType == enumType)
                            classification |= TypeClassification.IsEnum | TypeClassification.IsValueType;
                        if (baseType == typeof(MulticastDelegate))
                            classification |= TypeClassification.IsDelegate;
                        if (baseType == valueType && this.ToType() != enumType)
                        {
                            classification |= TypeClassification.IsValueType;
                            if (IsPrimitiveType(this.ToType()))
                                classification |= TypeClassification.IsPrimitive;
                        }
                    }
                    _lazyClassification = classification;
                }
                return _lazyClassification;
            }
        }

        [Flags]
        private enum TypeClassification
        {
            Computed = 0x00000001,    // Always set (to indicate that the lazy evaluation has occurred)
            IsValueType = 0x00000002,
            IsEnum = 0x00000004,
            IsPrimitive = 0x00000008,
            IsDelegate = 0x00000010,
        }

        private volatile TypeClassification _lazyClassification;

#if DEBUG
        private string _debugName;
#endif
    }
}