File: Language\TagHelperBinder.TagHelperSet.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.Diagnostics;
 
namespace Microsoft.AspNetCore.Razor.Language;
 
internal sealed partial class TagHelperBinder
{
    /// <summary>
    ///  Similar to <see cref="ImmutableArray{T}"/>, but optimized to store either a single value or an array of values.
    /// </summary>
    [DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
    [DebuggerTypeProxy(typeof(DebuggerProxy))]
    private readonly struct TagHelperSet
    {
        public static readonly TagHelperSet Empty = default!;
 
        private readonly object _valueOrArray;
 
        public TagHelperSet(TagHelperDescriptor value)
        {
            _valueOrArray = value;
        }
 
        public TagHelperSet(TagHelperDescriptor[] array)
        {
            _valueOrArray = array;
        }
 
        public TagHelperDescriptor this[int index]
        {
            get
            {
                return _valueOrArray switch
                {
                    TagHelperDescriptor[] array => array[index],
                    not null when index == 0 => (TagHelperDescriptor)_valueOrArray,
                    _ => throw new IndexOutOfRangeException(),
                };
            }
        }
 
        public int Count
            => _valueOrArray switch
            {
                TagHelperDescriptor[] array => array.Length,
                null => 0,
 
                // _valueOrArray can be an array, a single value, or null.
                // So, we can avoid a type check for the single value case.
                _ => 1
            };
 
        public Enumerator GetEnumerator()
            => new(this);
 
        public struct Enumerator
        {
            private readonly TagHelperSet _tagHelperSet;
            private int _index;
 
            internal Enumerator(TagHelperSet tagHelperSet)
            {
                _tagHelperSet = tagHelperSet;
                _index = -1;
            }
 
            public bool MoveNext()
            {
                _index++;
                return _index < _tagHelperSet.Count;
            }
 
            public readonly TagHelperDescriptor Current
                => _tagHelperSet[_index];
        }
 
        private sealed class DebuggerProxy(TagHelperSet instance)
        {
            private readonly TagHelperSet _instance = instance;
 
            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public TagHelperDescriptor[] Items
                => _instance._valueOrArray switch
                {
                    TagHelperDescriptor[] array => array,
                    TagHelperDescriptor value => [value],
                    _ => []
                };
        }
 
        private string GetDebuggerDisplay()
            => "Count " + Count;
 
        /// <summary>
        ///  This is a mutable builder for <see cref="TagHelperSet"/>. However, it works differently from
        ///  a typical builder. First, you must call <see cref="IncreaseSize"/> to set the number of items.
        ///  Once you've done that for each item to be added, you can call <see cref="Add(TagHelperDescriptor)"/>
        ///  exactly that many times. This ensures that space allocated is exactly what's needed to
        ///  produce the resulting <see cref="TagHelperSet"/>.
        /// </summary>
        public struct Builder
        {
            private object? _valueOrArray;
            private int _index;
            private int _size;
 
            public void IncreaseSize()
            {
                Debug.Assert(_valueOrArray is null, "Cannot increase size once items have been added.");
                _size++;
            }
 
            public void Add(TagHelperDescriptor item)
            {
                Debug.Assert(_index < _size, "Cannot add more items.");
 
                if (_size == 1)
                {
                    // We only need to store a single value.
                    _valueOrArray = item;
                    _index = 1;
                    return;
                }
 
                Debug.Assert(_valueOrArray is null or TagHelperDescriptor[]);
 
                if (_valueOrArray is not TagHelperDescriptor[] array)
                {
                    array = new TagHelperDescriptor[_size];
                    _valueOrArray = array;
                }
 
                array[_index++] = item;
            }
 
            public readonly TagHelperSet ToSet()
            {
                Debug.Assert(_index == _size, "Must have added all items.");
 
                return _size switch
                {
                    0 => Empty,
                    1 => new((TagHelperDescriptor)_valueOrArray!),
                    _ => new((TagHelperDescriptor[])_valueOrArray!)
                };
            }
        }
    }
}