File: Internal\Reflection\Extensions\NonPortable\CustomAttributeSearcher.cs
Web Access
Project: src\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;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Runtime.General;

//==================================================================================================================
// Dependency note:
//   This class must depend only on the CustomAttribute properties that return IEnumerable<CustomAttributeData>.
//   All of the other custom attribute api route back here so calls to them will cause an infinite recursion.
//==================================================================================================================

namespace Internal.Reflection.Extensions.NonPortable
{
    //
    // Common logic for computing the effective set of custom attributes on a reflection element of type E where
    // E is a MemberInfo, ParameterInfo, Assembly or Module.
    //
    // This class is only used by the CustomAttributeExtensions class - hence, we bake in the CustomAttributeExtensions behavior of
    // filtering out WinRT attributes.
    //
    internal abstract class CustomAttributeSearcher<E>
        where E : class
    {
        //
        // Returns the effective set of custom attributes on a reflection element.
        //
        public IEnumerable<CustomAttributeData> GetMatchingCustomAttributes(E element, Type optionalAttributeTypeFilter, bool inherit, bool skipTypeValidation = false)
        {
            // Do all parameter validation here before we enter the iterator function (so that exceptions from validations
            // show up immediately rather than on the first MoveNext()).
            ArgumentNullException.ThrowIfNull(element);

            bool typeFilterKnownToBeSealed = false;
            if (!skipTypeValidation)
            {
                ArgumentNullException.ThrowIfNull(optionalAttributeTypeFilter, "type");
                if (!(optionalAttributeTypeFilter == typeof(Attribute) ||
                      optionalAttributeTypeFilter.IsSubclassOf(typeof(Attribute))))
                    throw new ArgumentException(SR.Argument_MustHaveAttributeBaseClass);

                typeFilterKnownToBeSealed = optionalAttributeTypeFilter.IsSealed;
            }

            Func<Type, bool> passesFilter;
            if (optionalAttributeTypeFilter == null)
            {
                passesFilter =
                    delegate (Type actualType)
                    {
                        return true;
                    };
            }
            else if (optionalAttributeTypeFilter.IsGenericTypeDefinition)
            {
                passesFilter =
                    delegate (Type actualType)
                    {
                        if (actualType.IsConstructedGenericType && actualType.GetGenericTypeDefinition() == optionalAttributeTypeFilter)
                        {
                            return true;
                        }

                        if (!typeFilterKnownToBeSealed)
                        {
                            for (Type? type = actualType.BaseType; type != null; type = type.BaseType)
                            {
                                if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == optionalAttributeTypeFilter)
                                {
                                    return true;
                                }
                            }
                        }

                        return false;
                    };
            }
            else
            {
                passesFilter =
                    delegate (Type actualType)
                    {
                        if (optionalAttributeTypeFilter.Equals(actualType))
                            return true;
                        if (typeFilterKnownToBeSealed)
                            return false;
                        return optionalAttributeTypeFilter.IsAssignableFrom(actualType);
                    };
            }

            return GetMatchingCustomAttributesIterator(element, passesFilter, inherit);
        }

        //
        // Subclasses should override this to compute the "parent" of the element for the purpose of finding "inherited" custom attributes.
        // Return null if no parent.
        //
        public virtual E GetParent(E e)
        {
            return null;
        }


        //
        // Main iterator.
        //
        private IEnumerable<CustomAttributeData> GetMatchingCustomAttributesIterator(E element, Func<Type, bool> passesFilter, bool inherit)
        {
            ListBuilder<CustomAttributeData> immediateResults = default;
            foreach (CustomAttributeData cad in GetDeclaredCustomAttributes(element))
            {
                if (passesFilter(cad.AttributeType))
                {
                    yield return cad;

                    if (inherit)
                        immediateResults.Add(cad);
                }
            }
            if (inherit)
            {
                // Because the "inherit" parameter defaults to "true", we probably get here for a lot of elements that
                // don't actually have any inheritance chains. Try to avoid doing any unnecessary setup for the inheritance walk
                // unless we have to.
                element = GetParent(element);
                if (element != null)
                {
                    // This dictionary serves two purposes:
                    //   - Let us know which attribute types we've encountered at lower levels so we can block them from appearing twice in the results
                    //     if appropriate.
                    //
                    //   - Cache the results of retrieving the usage attribute.
                    //
                    LowLevelDictionary<TypeUnificationKey, AttributeUsageAttribute> encounteredTypes = new LowLevelDictionary<TypeUnificationKey, AttributeUsageAttribute>(11);

                    for (int i = 0; i < immediateResults.Count; i++)
                    {
                        Type attributeType = immediateResults[i].AttributeType;
                        TypeUnificationKey attributeTypeKey = new TypeUnificationKey(attributeType);
                        if (!encounteredTypes.TryGetValue(attributeTypeKey, out _))
                            encounteredTypes.Add(attributeTypeKey, null);
                    }

                    do
                    {
                        foreach (CustomAttributeData cad in GetDeclaredCustomAttributes(element))
                        {
                            Type attributeType = cad.AttributeType;
                            if (!passesFilter(attributeType))
                                continue;
                            AttributeUsageAttribute? usage;
                            TypeUnificationKey attributeTypeKey = new TypeUnificationKey(attributeType);
                            if (!encounteredTypes.TryGetValue(attributeTypeKey, out usage))
                            {
                                // Type was not encountered before. Only include it if it is inheritable.
                                usage = GetAttributeUsage(attributeType);
                                encounteredTypes.Add(attributeTypeKey, usage);
                                if (usage.Inherited)
                                    yield return cad;
                            }
                            else
                            {
                                usage ??= GetAttributeUsage(attributeType);
                                encounteredTypes[attributeTypeKey] = usage;
                                // Type was encountered at a lower level. Only include it if its inheritable AND allowMultiple.
                                if (usage.Inherited && usage.AllowMultiple)
                                    yield return cad;
                            }
                        }
                    }
                    while ((element = GetParent(element)) != null);
                }
            }
        }

        //
        // Internal helper to compute the AttributeUsage. This must be coded specially to avoid an infinite recursion.
        //
        private static AttributeUsageAttribute GetAttributeUsage(Type attributeType)
        {
            // This is only invoked when the seacher is called with "inherit: true", thus calling the searcher again
            // with "inherit: false" will not cause infinite recursion.
            //
            // Legacy: Why aren't we checking the parent types? Answer: Although AttributeUsageAttribute is itself marked inheritable, desktop Reflection
            // treats it as *non*-inheritable for the purpose of deciding whether another attribute class is inheritable.
            //
            AttributeUsageAttribute? usage = attributeType.GetCustomAttribute<AttributeUsageAttribute>(inherit: false);
            if (usage == null)
                return new AttributeUsageAttribute(AttributeTargets.All) { AllowMultiple = false, Inherited = true };
            return usage;
        }

        //
        // Subclasses must implement this to call the element-specific CustomAttributes property.
        //
        protected abstract IEnumerable<CustomAttributeData> GetDeclaredCustomAttributes(E element);
    }
}