File: System\ComponentModel\Composition\ReflectionModel\GenericServices.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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
 
namespace System.ComponentModel.Composition.ReflectionModel
{
    internal static class GenericServices
    {
        internal static IList<Type> GetPureGenericParameters(this Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            if (type.IsGenericType && type.ContainsGenericParameters)
            {
                List<Type> pureGenericParameters = new List<Type>();
                TraverseGenericType(type, (Type t) =>
                {
                    if (t.IsGenericParameter)
                    {
                        pureGenericParameters.Add(t);
                    }
                });
                return pureGenericParameters;
            }
            else
            {
                return Type.EmptyTypes;
            }
        }
 
        internal static int GetPureGenericArity(this Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            int genericArity = 0;
            if (type.IsGenericType && type.ContainsGenericParameters)
            {
                TraverseGenericType(type, (Type t) =>
                {
                    if (t.IsGenericParameter)
                    {
                        genericArity++;
                    }
                });
            }
            return genericArity;
        }
 
        private static void TraverseGenericType(Type type, Action<Type> onType)
        {
            if (type.IsGenericType)
            {
                foreach (Type genericArgument in type.GetGenericArguments())
                {
                    TraverseGenericType(genericArgument, onType);
                }
            }
            onType(type);
        }
 
        public static int[] GetGenericParametersOrder(Type type)
        {
            return type.GetPureGenericParameters().Select(parameter => parameter.GenericParameterPosition).ToArray();
        }
 
        public static string GetGenericName(string originalGenericName, int[] genericParametersOrder, int genericArity)
        {
            string[] genericFormatArgs = new string[genericArity];
            for (int i = 0; i < genericParametersOrder.Length; i++)
            {
                genericFormatArgs[genericParametersOrder[i]] = $"{{{i}}}";
            }
            return string.Format(CultureInfo.InvariantCulture, originalGenericName, genericFormatArgs);
        }
 
        public static T[] Reorder<T>(T[] original, int[] genericParametersOrder)
        {
            T[] genericSpecialization = new T[genericParametersOrder.Length];
            for (int i = 0; i < genericParametersOrder.Length; i++)
            {
                genericSpecialization[i] = original[genericParametersOrder[i]];
            }
            return genericSpecialization;
        }
 
        [return: NotNullIfNotNull(nameof(types))]
        public static IEnumerable<Type>? CreateTypeSpecializations(this Type[]? types, Type[] specializationTypes)
        {
            if (types == null)
            {
                return null;
            }
            else
            {
                return types.Select(type => type.CreateTypeSpecialization(specializationTypes));
            }
        }
 
        public static Type CreateTypeSpecialization(this Type type, Type[] specializationTypes)
        {
            if (!type.ContainsGenericParameters)
            {
                return type;
            }
 
            if (type.IsGenericParameter)
            {
                // the only case when MakeGenericType won't work is when the 'type' represents a "naked" generic type
                // in this case we simply grab the type with the proper index from the specializtion
                return specializationTypes[type.GenericParameterPosition];
            }
            else
            {
                Type[] typeGenericArguments = type.GetGenericArguments();
                Type[] subSpecialization = new Type[typeGenericArguments.Length];
 
                for (int i = 0; i < typeGenericArguments.Length; i++)
                {
                    Type typeGenericArgument = typeGenericArguments[i];
                    subSpecialization[i] = typeGenericArgument.IsGenericParameter ?
                        specializationTypes[typeGenericArgument.GenericParameterPosition] : typeGenericArgument;
 
                }
 
                // and "close" the generic
                return type.GetGenericTypeDefinition().MakeGenericType(subSpecialization);
            }
 
        }
 
        public static bool CanSpecialize(Type? type, IEnumerable<Type>? constraints, GenericParameterAttributes attributes)
        {
            return CanSpecialize(type, constraints) && CanSpecialize(type!, attributes);
        }
 
        public static bool CanSpecialize(Type? type, IEnumerable<Type>? constraintTypes)
        {
            if (constraintTypes == null)
            {
                return true;
            }
 
            // where T : IFoo
            // a part of where T : struct is also handled here as T : ValueType
            foreach (Type constraintType in constraintTypes)
            {
                if ((constraintType != null) && !constraintType.IsAssignableFrom(type))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        public static bool CanSpecialize(Type type, GenericParameterAttributes attributes)
        {
            if (attributes == GenericParameterAttributes.None)
            {
                return true;
            }
 
            // where T : class
            if ((attributes & GenericParameterAttributes.ReferenceTypeConstraint) != 0)
            {
                if (type.IsValueType)
                {
                    return false;
                }
            }
 
            // where T : new
            if ((attributes & GenericParameterAttributes.DefaultConstructorConstraint) != 0)
            {
                // value types always have default constructors
                if (!type.IsValueType && ((type.GetConstructor(Type.EmptyTypes) == null) || type.IsAbstract))
                {
                    return false;
                }
            }
 
            // where T : struct
            if ((attributes & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
            {
                // must be a value type
                if (!type.IsValueType)
                {
                    return false;
                }
 
                // Make sure that the type is not nullable
                // this is salways guaranteed in C#, but other languages may be different
                if (Nullable.GetUnderlyingType(type) != null)
                {
                    return false;
                }
            }
 
            // all other fals indicate variance and don't place any actual restrictions on the generic parameters
            // but rather how they should be used by the compiler
            return true;
        }
    }
}