File: System\Text\RegularExpressions\GroupCollection.cs
Web Access
Project: src\src\libraries\System.Text.RegularExpressions\src\System.Text.RegularExpressions.csproj (System.Text.RegularExpressions)
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
 
namespace System.Text.RegularExpressions
{
    /// <summary>
    /// Represents a sequence of capture substrings. The object is used
    /// to return the set of captures done by a single capturing group.
    /// </summary>
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(CollectionDebuggerProxy<Group>))]
    public class GroupCollection : IList<Group>, IReadOnlyList<Group>, IList, IReadOnlyDictionary<string, Group>
    {
        private readonly Match _match;
        private readonly Hashtable? _captureMap;
 
        /// <summary>Cache of Group objects fed to the user.</summary>
        private Group[]? _groups;
 
        internal GroupCollection(Match match, Hashtable? caps)
        {
            _match = match;
            _captureMap = caps;
        }
 
        internal void Reset() => _groups = null;
 
        public bool IsReadOnly => true;
 
        /// <summary>Returns the number of groups.</summary>
        public int Count => _match._matchcount.Length;
 
        public Group this[int groupnum] => GetGroup(groupnum);
 
        public Group this[string groupname] => _match._regex is null ?
            Group.s_emptyGroup :
            GetGroup(_match._regex.GroupNumberFromName(groupname));
 
        /// <summary>Provides an enumerator in the same order as Item[].</summary>
        public IEnumerator GetEnumerator() => new Enumerator(this);
 
        IEnumerator<Group> IEnumerable<Group>.GetEnumerator() => new Enumerator(this);
 
        private Group GetGroup(int groupnum)
        {
            if (_captureMap != null)
            {
                if (_captureMap.TryGetValue(groupnum, out int groupNumImpl))
                {
                    return GetGroupImpl(groupNumImpl);
                }
            }
            else if ((uint)groupnum < _match._matchcount.Length)
            {
                return GetGroupImpl(groupnum);
            }
 
            return Group.s_emptyGroup;
        }
 
        /// <summary>
        /// Caches the group objects
        /// </summary>
        private Group GetGroupImpl(int groupnum)
        {
            if (groupnum == 0)
            {
                return _match;
            }
 
            // Construct all the Group objects the first time GetGroup is called
            if (_groups is null)
            {
                _groups = new Group[_match._matchcount.Length - 1];
                for (int i = 0; i < _groups.Length; i++)
                {
                    string groupname = _match._regex!.GroupNameFromNumber(i + 1);
                    _groups[i] = new Group(_match.Text, _match._matches[i + 1], _match._matchcount[i + 1], groupname);
                }
            }
 
            return _groups[groupnum - 1];
        }
 
        public bool IsSynchronized => false;
 
        public object SyncRoot => _match;
 
        public void CopyTo(Array array, int arrayIndex)
        {
            if (array is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
            }
 
            for (int i = arrayIndex, j = 0; j < Count; i++, j++)
            {
                array.SetValue(this[j], i);
            }
        }
 
        public void CopyTo(Group[] array, int arrayIndex)
        {
            if (array is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
            }
 
            ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(arrayIndex, array.Length);
 
            if (array.Length - arrayIndex < Count)
            {
                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
            }
 
            for (int i = arrayIndex, j = 0; j < Count; i++, j++)
            {
                array[i] = this[j];
            }
        }
 
        int IList<Group>.IndexOf(Group item)
        {
            for (int i = 0; i < Count; i++)
            {
                if (EqualityComparer<Group>.Default.Equals(this[i], item))
                {
                    return i;
                }
            }
 
            return -1;
        }
 
        void IList<Group>.Insert(int index, Group item) =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        void IList<Group>.RemoveAt(int index) =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        Group IList<Group>.this[int index]
        {
            get => this[index];
            set => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
        }
 
        void ICollection<Group>.Add(Group item) =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        void ICollection<Group>.Clear() =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        bool ICollection<Group>.Contains(Group item) =>
            ((IList<Group>)this).IndexOf(item) >= 0;
 
        bool ICollection<Group>.Remove(Group item) =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        int IList.Add(object? value) =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        void IList.Clear() =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        bool IList.Contains(object? value) =>
            value is Group other && ((ICollection<Group>)this).Contains(other);
 
        int IList.IndexOf(object? value) =>
            value is Group other ? ((IList<Group>)this).IndexOf(other) : -1;
 
        void IList.Insert(int index, object? value) =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        bool IList.IsFixedSize => true;
 
        void IList.Remove(object? value) =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        void IList.RemoveAt(int index) =>
            throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
 
        object? IList.this[int index]
        {
            get => this[index];
            set => throw new NotSupportedException(SR.NotSupported_ReadOnlyCollection);
        }
 
        IEnumerator<KeyValuePair<string, Group>> IEnumerable<KeyValuePair<string, Group>>.GetEnumerator() =>
            new Enumerator(this);
 
        public bool TryGetValue(string key, [NotNullWhen(true)] out Group? value)
        {
            Group group = this[key];
            if (group == Group.s_emptyGroup)
            {
                value = null;
                return false;
            }
 
            value = group;
            return true;
        }
 
        public bool ContainsKey(string key) => _match._regex!.GroupNumberFromName(key) >= 0;
 
        public IEnumerable<string> Keys
        {
            get
            {
                for (int i = 0; i < Count; ++i)
                {
                    yield return GetGroup(i).Name;
                }
            }
        }
 
        public IEnumerable<Group> Values
        {
            get
            {
                for (int i = 0; i < Count; ++i)
                {
                    yield return GetGroup(i);
                }
            }
        }
 
        private sealed class Enumerator : IEnumerator<Group>, IEnumerator<KeyValuePair<string, Group>>
        {
            private readonly GroupCollection _collection;
            private int _index;
 
            internal Enumerator(GroupCollection collection)
            {
                Debug.Assert(collection != null, "collection cannot be null.");
 
                _collection = collection;
                _index = -1;
            }
 
            public bool MoveNext()
            {
                int size = _collection.Count;
 
                if (_index >= size)
                {
                    return false;
                }
 
                _index++;
                return _index < size;
            }
 
            public Group Current
            {
                get
                {
                    if (_index < 0 || _index >= _collection.Count)
                    {
                        throw new InvalidOperationException(SR.EnumNotStarted);
                    }
 
                    return _collection[_index];
                }
            }
 
            KeyValuePair<string, Group> IEnumerator<KeyValuePair<string, Group>>.Current
            {
                get
                {
                    if ((uint)_index >= _collection.Count)
                    {
                        throw new InvalidOperationException(SR.EnumNotStarted);
                    }
 
                    Group value = _collection[_index];
                    return new KeyValuePair<string, Group>(value.Name, value);
 
                }
            }
 
            object IEnumerator.Current => Current;
 
            void IEnumerator.Reset() => _index = -1;
 
            void IDisposable.Dispose() { }
        }
    }
}