File: Language\TagHelperCollection.SegmentCollectionBase.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler)
// 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.AspNetCore.Razor.Threading;
using Microsoft.AspNetCore.Razor.Utilities;
 
namespace Microsoft.AspNetCore.Razor.Language;
 
public abstract partial class TagHelperCollection
{
    /// <summary>
    ///  Represents an abstract collection of tag helper descriptors organized into segments.
    /// </summary>
    /// <remarks>
    ///  SegmentCollection provides efficient lookup and indexing for tag helper descriptors by
    ///  utilizing a segmented internal structure. This class is intended to be used as a base
    ///  for specialized collections that require optimized access patterns for large numbers of
    ///  tag helpers. Thread safety and mutability depend on the implementation of the derived class.
    /// </remarks>
    private abstract class SegmentCollectionBase : TagHelperCollection
    {
        private LazyValue<TagHelperCollection, Dictionary<Checksum, int>> _lazyLookupTable = new(collection =>
        {
            var lookupTable = new Dictionary<Checksum, int>(collection.Count);
            var index = 0;
 
            foreach (var segment in collection.Segments)
            {
                foreach (var item in segment.Span)
                {
                    lookupTable.Add(item.Checksum, index++);
                }
            }
 
            return lookupTable;
        });
 
        private LazyValue<TagHelperCollection, Checksum> _lazyChecksum = new(collection =>
        {
            var builder = new Checksum.Builder();
 
            foreach (var segment in collection.Segments)
            {
                foreach (var item in segment.Span)
                {
                    builder.Append(item.Checksum);
                }
            }
 
            return builder.FreeAndGetChecksum();
        });
 
        private bool UseLookupTable => Count > 8;
 
        private Dictionary<Checksum, int> LookupTable
        {
            get
            {
                Debug.Assert(UseLookupTable);
                return _lazyLookupTable.GetValue(this);
            }
        }
 
        internal override Checksum Checksum
            => _lazyChecksum.GetValue(this);
 
        public override int IndexOf(TagHelperDescriptor item)
        {
            if (UseLookupTable)
            {
                return LookupTable.TryGetValue(item.Checksum, out var index)
                    ? index
                    : -1;
            }
 
            var currentOffset = 0;
 
            foreach (var segment in Segments)
            {
                var index = segment.Span.IndexOf(item);
 
                if (index >= 0)
                {
                    return currentOffset + index;
                }
 
                currentOffset += segment.Length;
            }
 
            return -1;
        }
 
        public override void CopyTo(Span<TagHelperDescriptor> destination)
        {
            ArgHelper.ThrowIfDestinationTooShort(destination, Count);
 
            foreach (var segment in Segments)
            {
                segment.Span.CopyTo(destination);
                destination = destination[segment.Length..];
            }
        }
    }
}