File: System\ComponentModel\Composition\ContractNameServices.cs
Web Access
Project: src\src\libraries\System.ComponentModel.Composition\src\System.ComponentModel.Composition.csproj (System.ComponentModel.Composition)
// 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.Reflection;
using System.Text;
using Microsoft.Internal;
 
namespace System.ComponentModel.Composition
{
    internal static class ContractNameServices
    {
        private const char NamespaceSeparator = '.';
        private const char ArrayOpeningBracket = '[';
        private const char ArrayClosingBracket = ']';
        private const char ArraySeparator = ',';
        private const char PointerSymbol = '*';
        private const char ReferenceSymbol = '&';
        private const char GenericArityBackQuote = '`';
        private const char NestedClassSeparator = '+';
        private const char ContractNameGenericOpeningBracket = '(';
        private const char ContractNameGenericClosingBracket = ')';
        private const char ContractNameGenericArgumentSeparator = ',';
        private const char CustomModifiersSeparator = ' ';
        private const char GenericFormatOpeningBracket = '{';
        private const char GenericFormatClosingBracket = '}';
 
        [ThreadStatic]
        private static Dictionary<Type, string>? typeIdentityCache;
 
        private static Dictionary<Type, string> TypeIdentityCache
        {
            get
            {
                return typeIdentityCache ??= new Dictionary<Type, string>();
            }
        }
 
        internal static string GetTypeIdentity(Type type)
        {
            return GetTypeIdentity(type, true);
        }
 
        internal static string GetTypeIdentity(Type type, bool formatGenericName)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            if (!TypeIdentityCache.TryGetValue(type, out string? typeIdentity))
            {
                if (!type.IsAbstract && type.HasBaseclassOf(typeof(Delegate)))
                {
                    MethodInfo method = type.GetMethod("Invoke")!;
                    typeIdentity = ContractNameServices.GetTypeIdentityFromMethod(method);
                }
                else if (type.IsGenericParameter)
                {
                    StringBuilder typeIdentityStringBuilder = new StringBuilder();
                    WriteTypeArgument(typeIdentityStringBuilder, false, type, formatGenericName);
                    typeIdentityStringBuilder.Remove(typeIdentityStringBuilder.Length - 1, 1);
                    typeIdentity = typeIdentityStringBuilder.ToString();
                }
                else
                {
                    StringBuilder typeIdentityStringBuilder = new StringBuilder();
                    WriteTypeWithNamespace(typeIdentityStringBuilder, type, formatGenericName);
                    typeIdentity = typeIdentityStringBuilder.ToString();
                }
 
                if (string.IsNullOrEmpty(typeIdentity))
                {
                    throw new Exception(SR.Diagnostic_InternalExceptionMessage);
                }
                TypeIdentityCache.Add(type, typeIdentity);
            }
 
            return typeIdentity;
        }
 
        internal static string GetTypeIdentityFromMethod(MethodInfo method)
        {
            return GetTypeIdentityFromMethod(method, true);
        }
 
        internal static string GetTypeIdentityFromMethod(MethodInfo method, bool formatGenericName)
        {
            StringBuilder methodNameStringBuilder = new StringBuilder();
 
            WriteTypeWithNamespace(methodNameStringBuilder, method.ReturnType, formatGenericName);
 
            methodNameStringBuilder.Append('(');
 
            ParameterInfo[] parameters = method.GetParameters();
 
            for (int i = 0; i < parameters.Length; i++)
            {
                if (i != 0)
                {
                    methodNameStringBuilder.Append(',');
                }
 
                WriteTypeWithNamespace(methodNameStringBuilder, parameters[i].ParameterType, formatGenericName);
            }
            methodNameStringBuilder.Append(')');
 
            return methodNameStringBuilder.ToString();
        }
 
        private static void WriteTypeWithNamespace(StringBuilder typeName, Type type, bool formatGenericName)
        {
            // Writes type with namesapce
            if (!string.IsNullOrEmpty(type.Namespace))
            {
                typeName.Append(type.Namespace);
                typeName.Append(NamespaceSeparator);
            }
            WriteType(typeName, type, formatGenericName);
        }
 
        private static void WriteType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            // Writes type name
            if (type.IsGenericType)
            {
                //
                // Reflection format stores all the generic arguments (including the ones for parent types) on the leaf type.
                // These arguments are placed in a queue and are written out based on generic arity (`X) of each type
                //
                Queue<Type> genericTypeArguments = new Queue<Type>(type.GetGenericArguments());
                WriteGenericType(typeName, type, type.IsGenericTypeDefinition, genericTypeArguments, formatGenericName);
                if (genericTypeArguments.Count != 0)
                {
                    throw new Exception(SR.Expecting_Empty_Queue);
                }
            }
            else
            {
                WriteNonGenericType(typeName, type, formatGenericName);
            }
        }
 
        private static void WriteNonGenericType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            //
            // Writes non-generic type
            //
            if (type.DeclaringType != null)
            {
                WriteType(typeName, type.DeclaringType, formatGenericName);
                typeName.Append(NestedClassSeparator);
            }
            if (type.IsArray)
            {
                WriteArrayType(typeName, type, formatGenericName);
            }
            else if (type.IsPointer)
            {
                WritePointerType(typeName, type, formatGenericName);
            }
            else if (type.IsByRef)
            {
                WriteByRefType(typeName, type, formatGenericName);
            }
            else
            {
                typeName.Append(type.Name);
            }
        }
 
        private static void WriteArrayType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            //
            // Writes array type  e.g <TypeName>[]
            // Note that jagged arrays are stored in reverse order
            // e.g. C#: Int32[][,]  Reflection: Int32[,][]
            // we are following C# order for arrays
            //
            Type rootElementType = FindArrayElementType(type);
            WriteType(typeName, rootElementType, formatGenericName);
            Type? elementType = type;
            do
            {
                WriteArrayTypeDimensions(typeName, elementType);
            }
            while ((elementType = elementType.GetElementType()) != null && elementType.IsArray);
        }
 
        private static void WritePointerType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            //
            // Writes pointer type  e.g <TypeName>*
            //
            WriteType(typeName, type.GetElementType()!, formatGenericName);
            typeName.Append(PointerSymbol);
        }
 
        private static void WriteByRefType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            //
            // Writes by ref type e.g <TypeName>&
            //
            WriteType(typeName, type.GetElementType()!, formatGenericName);
            typeName.Append(ReferenceSymbol);
        }
 
        private static void WriteArrayTypeDimensions(StringBuilder typeName, Type type)
        {
            //
            // Writes array type dimensions e.g. [,,]
            //
            typeName.Append(ArrayOpeningBracket);
            int rank = type.GetArrayRank();
            for (int i = 1; i < rank; i++)
            {
                typeName.Append(ArraySeparator);
            }
            typeName.Append(ArrayClosingBracket);
        }
 
        private static void WriteGenericType(StringBuilder typeName, Type type, bool isDefinition, Queue<Type> genericTypeArguments, bool formatGenericName)
        {
            //
            // Writes generic type including parent generic types
            // genericTypeArguments contains type arguments obtained from the most nested type
            // isDefinition parameter indicates if we are dealing with generic type definition
            //
            if (type.DeclaringType != null)
            {
                if (type.DeclaringType.IsGenericType)
                {
                    WriteGenericType(typeName, type.DeclaringType, isDefinition, genericTypeArguments, formatGenericName);
                }
                else
                {
                    WriteNonGenericType(typeName, type.DeclaringType, formatGenericName);
                }
                typeName.Append(NestedClassSeparator);
            }
            WriteGenericTypeName(typeName, type, isDefinition, genericTypeArguments, formatGenericName);
        }
 
        private static void WriteGenericTypeName(StringBuilder typeName, Type type, bool isDefinition, Queue<Type> genericTypeArguments, bool formatGenericName)
        {
            //
            // Writes generic type name, e.g. generic name and generic arguments
            //
            if (!type.IsGenericType)
            {
                throw new Exception(SR.Expecting_Generic_Type);
            }
            int genericArity = GetGenericArity(type);
            string genericTypeName = FindGenericTypeName(type.GetGenericTypeDefinition().Name);
            typeName.Append(genericTypeName);
            WriteTypeArgumentsString(typeName, genericArity, isDefinition, genericTypeArguments, formatGenericName);
        }
 
        private static void WriteTypeArgumentsString(StringBuilder typeName, int argumentsCount, bool isDefinition, Queue<Type> genericTypeArguments, bool formatGenericName)
        {
            //
            // Writes type arguments in brackets, e.g. (<contract_name1>, <contract_name2>, ...)
            //
            if (argumentsCount == 0)
            {
                return;
            }
            typeName.Append(ContractNameGenericOpeningBracket);
            for (int i = 0; i < argumentsCount; i++)
            {
                if (genericTypeArguments.Count == 0)
                {
                    throw new Exception(SR.Expecting_AtleastOne_Type);
                }
                Type genericTypeArgument = genericTypeArguments.Dequeue();
                WriteTypeArgument(typeName, isDefinition, genericTypeArgument, formatGenericName);
            }
            typeName.Remove(typeName.Length - 1, 1);
            typeName.Append(ContractNameGenericClosingBracket);
        }
 
        private static void WriteTypeArgument(StringBuilder typeName, bool isDefinition, Type genericTypeArgument, bool formatGenericName)
        {
            if (!isDefinition && !genericTypeArgument.IsGenericParameter)
            {
                WriteTypeWithNamespace(typeName, genericTypeArgument, formatGenericName);
            }
 
            if (formatGenericName && genericTypeArgument.IsGenericParameter)
            {
                typeName.Append(GenericFormatOpeningBracket);
                typeName.Append(genericTypeArgument.GenericParameterPosition);
                typeName.Append(GenericFormatClosingBracket);
            }
            typeName.Append(ContractNameGenericArgumentSeparator);
        }
 
        //internal for testability
        internal static void WriteCustomModifiers(StringBuilder typeName, string customKeyword, Type[] types, bool formatGenericName)
        {
            //
            // Writes custom modifiers in the format: customKeyword(<contract_name>,<contract_name>,...)
            //
            typeName.Append(CustomModifiersSeparator);
            typeName.Append(customKeyword);
            Queue<Type> typeArguments = new Queue<Type>(types);
            WriteTypeArgumentsString(typeName, types.Length, false, typeArguments, formatGenericName);
            if (typeArguments.Count != 0)
            {
                throw new Exception(SR.Expecting_Empty_Queue);
            }
        }
 
        private static Type FindArrayElementType(Type type)
        {
            //
            // Gets array element type by calling GetElementType() until the element is not an array
            //
            Type? elementType = type;
            while ((elementType = elementType.GetElementType()) != null && elementType.IsArray) { }
            return elementType!;
        }
 
        private static string FindGenericTypeName(string genericName)
        {
            //
            // Gets generic type name omitting the backquote and arity indicator
            // List`1 -> List
            // Arity indicator is returned as output parameter
            //
            int indexOfBackQuote = genericName.IndexOf(GenericArityBackQuote);
            if (indexOfBackQuote > -1)
            {
                genericName = genericName.Substring(0, indexOfBackQuote);
            }
            return genericName;
        }
 
        private static int GetGenericArity(Type type)
        {
            if (type.DeclaringType == null)
            {
                return type.GetGenericArguments().Length;
            }
 
            // The generic arity is equal to the difference in the number of generic arguments
            // from the type and the declaring type.
 
            int delclaringTypeGenericArguments = type.DeclaringType.GetGenericArguments().Length;
            int typeGenericArguments = type.GetGenericArguments().Length;
 
            if (typeGenericArguments < delclaringTypeGenericArguments)
            {
                throw new Exception(SR.Diagnostic_InternalExceptionMessage);
            }
 
            return typeGenericArguments - delclaringTypeGenericArguments;
        }
    }
}