File: System\Reflection\TypeLoading\CustomAttributes\CustomAttributeHelpers.cs
Web Access
Project: src\src\libraries\System.Reflection.MetadataLoadContext\src\System.Reflection.MetadataLoadContext.csproj (System.Reflection.MetadataLoadContext)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
 
namespace System.Reflection.TypeLoading
{
    internal static class CustomAttributeHelpers
    {
        /// <summary>
        /// Helper for creating a CustomAttributeNamedArgument.
        /// </summary>
        public static CustomAttributeNamedArgument ToCustomAttributeNamedArgument(this Type attributeType, string name, Type? argumentType, object? value)
        {
            MemberInfo[] members = attributeType.GetMember(name, MemberTypes.Field | MemberTypes.Property, BindingFlags.Public | BindingFlags.Instance);
            if (members.Length == 0)
                throw new MissingMemberException(attributeType.FullName, name);
            MemberInfo match = members[0];
            if (members.Length > 1)
                throw ThrowHelper.GetAmbiguousMatchException(match);
            return new CustomAttributeNamedArgument(match, new CustomAttributeTypedArgument(argumentType!, value));
        }
 
        /// <summary>
        /// Clones a cached CustomAttributeTypedArgument list into a freshly allocated one suitable for direct return through an api.
        /// </summary>
        public static ReadOnlyCollection<CustomAttributeTypedArgument> CloneForApiReturn(this IList<CustomAttributeTypedArgument> cats)
        {
            int count = cats.Count;
            CustomAttributeTypedArgument[] clones = count != 0 ? new CustomAttributeTypedArgument[count] : Array.Empty<CustomAttributeTypedArgument>();
            for (int i = 0; i < count; i++)
            {
                clones[i] = cats[i].CloneForApiReturn();
            }
            return Array.AsReadOnly(clones);
        }
 
        /// <summary>
        /// Clones a cached CustomAttributeNamedArgument list into a freshly allocated one suitable for direct return through an api.
        /// </summary>
        public static ReadOnlyCollection<CustomAttributeNamedArgument> CloneForApiReturn(this IList<CustomAttributeNamedArgument> cans)
        {
            int count = cans.Count;
            CustomAttributeNamedArgument[] clones = count != 0 ? new CustomAttributeNamedArgument[count] : Array.Empty<CustomAttributeNamedArgument>();
            for (int i = 0; i < count; i++)
            {
                clones[i] = cans[i].CloneForApiReturn();
            }
            return Array.AsReadOnly(clones);
        }
 
        /// <summary>
        /// Clones a cached CustomAttributeTypedArgument into a freshly allocated one suitable for direct return through an api.
        /// </summary>
        private static CustomAttributeTypedArgument CloneForApiReturn(this CustomAttributeTypedArgument cat)
        {
            Type type = cat.ArgumentType;
            object? value = cat.Value;
 
            if (!(value is IList<CustomAttributeTypedArgument> cats))
                return cat;
 
            int count = cats.Count;
            CustomAttributeTypedArgument[] cads = count != 0 ? new CustomAttributeTypedArgument[count] : Array.Empty<CustomAttributeTypedArgument>();
            for (int i = 0; i < count; i++)
            {
                cads[i] = cats[i].CloneForApiReturn();
            }
            return new CustomAttributeTypedArgument(type, Array.AsReadOnly(cads));
        }
 
        /// <summary>
        /// Clones a cached CustomAttributeNamedArgument into a freshly allocated one suitable for direct return through an api.
        /// </summary>
        private static CustomAttributeNamedArgument CloneForApiReturn(this CustomAttributeNamedArgument can)
        {
            return new CustomAttributeNamedArgument(can.MemberInfo, can.TypedValue.CloneForApiReturn());
        }
 
        /// <summary>
        /// Convert MarshalAsAttribute data into CustomAttributeData form. Returns null if the core assembly cannot be loaded or if the necessary
        /// types aren't in the core assembly.
        /// </summary>
        public static CustomAttributeData? TryComputeMarshalAsCustomAttributeData(Func<MarshalAsAttribute> marshalAsAttributeComputer, MetadataLoadContext loader)
        {
            // Make sure all the necessary framework types exist in this MetadataLoadContext's core assembly. If one doesn't, skip.
            CoreTypes ct = loader.GetAllFoundCoreTypes();
            if (ct[CoreType.String] == null ||
                ct[CoreType.Boolean] == null ||
                ct[CoreType.UnmanagedType] == null ||
                ct[CoreType.VarEnum] == null ||
                ct[CoreType.Type] == null ||
                ct[CoreType.Int16] == null ||
                ct[CoreType.Int32] == null)
                return null;
            ConstructorInfo? ci = loader.TryGetMarshalAsCtor();
            if (ci == null)
                return null;
 
            Func<CustomAttributeArguments> argumentsPromise =
                () =>
                {
                    // The expensive work goes in here. It will not execute unless someone invokes the Constructor/NamedArguments properties on
                    // the CustomAttributeData.
 
                    MarshalAsAttribute ma = marshalAsAttributeComputer();
 
                    Type attributeType = ci.DeclaringType!;
 
                    CustomAttributeTypedArgument[] cats = { new CustomAttributeTypedArgument(ct[CoreType.UnmanagedType]!, (int)(ma.Value)) };
                    List<CustomAttributeNamedArgument> cans = new List<CustomAttributeNamedArgument>();
                    cans.AddRange(new CustomAttributeNamedArgument[]
                    {
                        attributeType.ToCustomAttributeNamedArgument(nameof(MarshalAsAttribute.ArraySubType), ct[CoreType.UnmanagedType], (int)ma.ArraySubType),
                        attributeType.ToCustomAttributeNamedArgument(nameof(MarshalAsAttribute.IidParameterIndex), ct[CoreType.Int32], ma.IidParameterIndex),
                        attributeType.ToCustomAttributeNamedArgument(nameof(MarshalAsAttribute.SafeArraySubType), ct[CoreType.VarEnum], (int)ma.SafeArraySubType),
                        attributeType.ToCustomAttributeNamedArgument(nameof(MarshalAsAttribute.SizeConst), ct[CoreType.Int32], ma.SizeConst),
                        attributeType.ToCustomAttributeNamedArgument(nameof(MarshalAsAttribute.SizeParamIndex), ct[CoreType.Int16], ma.SizeParamIndex),
                    });
 
                    if (ma.SafeArrayUserDefinedSubType != null)
                    {
                        cans.Add(attributeType.ToCustomAttributeNamedArgument(nameof(MarshalAsAttribute.SafeArrayUserDefinedSubType), ct[CoreType.Type], ma.SafeArrayUserDefinedSubType));
                    }
 
                    if (ma.MarshalType != null)
                    {
                        cans.Add(attributeType.ToCustomAttributeNamedArgument(nameof(MarshalAsAttribute.MarshalType), ct[CoreType.String], ma.MarshalType));
                    }
 
                    if (ma.MarshalTypeRef != null)
                    {
                        cans.Add(attributeType.ToCustomAttributeNamedArgument(nameof(MarshalAsAttribute.MarshalTypeRef), ct[CoreType.Type], ma.MarshalTypeRef));
                    }
 
                    if (ma.MarshalCookie != null)
                    {
                        cans.Add(attributeType.ToCustomAttributeNamedArgument(nameof(MarshalAsAttribute.MarshalCookie), ct[CoreType.String], ma.MarshalCookie));
                    }
 
                    return new CustomAttributeArguments(cats, cans);
                };
 
            return new RoPseudoCustomAttributeData(ci, argumentsPromise);
        }
    }
}