File: System\Reflection\Runtime\BindingFlagSupport\QueryResult.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.Diagnostics;
 
namespace System.Reflection.Runtime.BindingFlagSupport
{
    //
    // Stores the result of a member filtering that's further filtered by the Public, NonPublic, Instance, Static and FlatternHierarchy bits of BindingFlags.
    // This object is not considered a candidate for long term caching.
    //
    // Note: The uninitialized state ("qr = default(QueryResult<M>)) is considered a valid state for this object, and represents an empty list of members.
    //
    internal partial struct QueryResult<M> where M : MemberInfo
    {
        public QueryResult(BindingFlags bindingAttr, QueriedMemberList<M> queriedMembers)
        {
            _lazyCount = 0;
            _bindingAttr = bindingAttr;
            _queriedMembers = queriedMembers;
        }
 
        public QueryResultEnumerator GetEnumerator() => new QueryResultEnumerator(this);
 
        /// <summary>
        /// Returns the number of matching results.
        /// </summary>
        public int Count
        {
            get
            {
                int count = _lazyCount;
                if (count == 0)
                {
                    if (_queriedMembers == null)
                        return 0;  // This is an uninitialized QueryResult<M>, which is supported and represents a 0-length list of matches.
 
                    int unfilteredCount = UnfilteredCount;
                    for (int i = 0; i < unfilteredCount; i++)
                    {
                        if (_queriedMembers.Matches(i, _bindingAttr))
                            count++;
                    }
 
                    if (count == 0)
                    {
                        // If no matches were found, set ourselves back to the "uninitialized" state so that future
                        // calls to Count won't go through this calculation again.
                        _queriedMembers = null;
                    }
 
                    _lazyCount = count;
 
                }
                return count;
            }
        }
 
        /// <summary>
        /// Copies the results to a freshly allocated array. Use this at api boundary points.
        /// </summary>
        public M[] ToArray()
        {
            int count = Count;
            if (count == 0)
                return Array.Empty<M>();
 
            M[] newArray = new M[count];
            CopyTo(newArray, 0);
            return newArray;
        }
 
        /// <summary>
        /// Copies the results into an existing array.
        /// </summary>
        public void CopyTo(MemberInfo[] array, int startIndex)
        {
            if (_queriedMembers == null)
                return; // This is an uninitialized QueryResult<M>, which is supported and represents a 0-length list of matches.
 
            int unfilteredCount = UnfilteredCount;
            for (int i = 0; i < unfilteredCount; i++)
            {
                if (_queriedMembers.Matches(i, _bindingAttr))
                {
                    array[startIndex++] = _queriedMembers[i];
                }
            }
        }
 
        /// <summary>
        /// Returns a single member, null or throws AmbiguousMatchException, for the Type.Get*(string name,...) family of apis.
        /// </summary>
        public M? Disambiguate()
        {
            if (_queriedMembers == null)
                return null; // This is an uninitialized QueryResult<M>, which is supported and represents a 0-length list of matches.
 
            int unfilteredCount = UnfilteredCount;
 
            M? match = null;
            for (int i = 0; i < unfilteredCount; i++)
            {
                if (_queriedMembers.Matches(i, _bindingAttr))
                {
                    if (match != null)
                    {
                        M challenger = _queriedMembers[i];
 
                        // Assuming the policy says it's ok to ignore the ambiguity, we're to resolve in favor of the member
                        // declared by the most derived type. Since QueriedMemberLists are sorted in order of decreasing derivation,
                        // that means we let the first match win - unless, of course, they're both the "most derived member".
                        if (match.DeclaringType!.Equals(challenger.DeclaringType))
                            throw ThrowHelper.GetAmbiguousMatchException(match);
 
                        MemberPolicies<M> policies = MemberPolicies<M>.Default;
                        if (!policies.OkToIgnoreAmbiguity(match, challenger))
                            throw ThrowHelper.GetAmbiguousMatchException(match);
                    }
                    else
                    {
                        match = _queriedMembers[i];
                    }
                }
            }
            return match;
        }
 
        private int UnfilteredCount => ((_bindingAttr & BindingFlags.DeclaredOnly) != 0) ? _queriedMembers!.DeclaredOnlyCount : _queriedMembers!.TotalCount;
 
        private readonly BindingFlags _bindingAttr;
        private int _lazyCount; // Intentionally not marking as volatile. QueryResult is for short-term use within a single method call - no aspiration to be thread-safe.
        private QueriedMemberList<M>? _queriedMembers;
    }
}