| File: System\Text\RegularExpressions\GroupCollection.cs | Web Access |
| Project: src\runtime\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> /// Returns the set of captured groups in a single match. The collection is immutable (read-only) and /// has no public constructor. /// </summary> /// <remarks> /// <para> /// The <see cref="GroupCollection" /> class is a zero-based collection class that consists of one or /// more <see cref="Group" /> objects that provide information about captured groups in a regular /// expression match. The collection is immutable (read-only) and has no public constructor. A /// <see cref="GroupCollection" /> object is returned by the <see cref="Match.Groups" /> property. /// </para> /// <para> /// The collection contains one or more <see cref="Group" /> objects. If the match is successful, the /// first element in the collection contains the <see cref="Group" /> object that corresponds to the /// entire match. Each subsequent element represents a captured group, if the regular expression /// includes capturing groups. Matches from numbered (unnamed) capturing groups appear in numeric /// order before matches from named capturing groups. If the match is unsuccessful, the collection /// contains a single <see cref="Group" /> object whose <see cref="Group.Success" /> property is /// <see langword="false" /> and whose <see cref="Capture.Value" /> property equals /// <see cref="string.Empty" />. /// </para> /// <para> /// To iterate through the members of the collection, you should use the collection iteration /// construct provided by your language (such as <c>foreach</c> in C#) instead of retrieving the /// enumerator that is returned by the <see cref="GetEnumerator" /> method. /// </para> /// </remarks> [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; /// <summary>Gets a value that indicates whether the collection is read only.</summary> /// <value><see langword="true" /> in all cases.</value> public bool IsReadOnly => true; /// <summary>Returns the number of groups in the collection.</summary> /// <value>The number of groups in the collection.</value> /// <remarks> /// The <see cref="GroupCollection" /> object always has at least one member. If a match is /// unsuccessful, the <see cref="Match.Groups" /> property returns a /// <see cref="GroupCollection" /> object that contains a single member. /// </remarks> public int Count => _match._matchcount.Length; /// <summary>Enables access to a member of the collection by integer index.</summary> /// <param name="groupnum">The zero-based index of the collection member to be retrieved.</param> /// <value>The member of the collection specified by <paramref name="groupnum" />.</value> /// <remarks> /// <para> /// This property is the indexer of the <see cref="GroupCollection" /> /// class. It allows you to enumerate the members of the collection by using a <c>foreach</c> /// statement. /// </para> /// <para> /// You can also use this property to retrieve individual captured groups by their index number. /// You can determine the number of items in the collection by retrieving the value of the /// <see cref="Count" /> property. Valid values for the <paramref name="groupnum" /> parameter /// range from 0 to one less than the number of items in the collection. /// </para> /// <para> /// If <paramref name="groupnum" /> is not the index of a member of the collection, or if /// <paramref name="groupnum" /> is the index of a capturing group that has not been matched in /// the input string, the method returns a <see cref="Group" /> object whose /// <see cref="Group.Success" /> property is <see langword="false" /> and whose /// <c>Group.Value</c> property is <see cref="string.Empty" />. /// </para> /// </remarks> public Group this[int groupnum] => GetGroup(groupnum); /// <summary>Enables access to a member of the collection by string index.</summary> /// <param name="groupname">The name of a capturing group.</param> /// <value>The member of the collection specified by <paramref name="groupname" />.</value> /// <remarks> /// <para> /// <paramref name="groupname" /> can be either the name of a capturing group that is defined by /// the <c>(?<name>)</c> element in a regular expression, or the string representation of /// the number of a capturing group. /// </para> /// <para> /// If <paramref name="groupname" /> is not the name of a capturing group in the collection, or /// if <paramref name="groupname" /> is the name of a capturing group that has not been matched in /// the input string, the method returns a <see cref="Group" /> object whose /// <see cref="Group.Success" /> property is <see langword="false" /> and whose /// <c>Group.Value</c> property is <see cref="string.Empty" />. /// </para> /// </remarks> public Group this[string groupname] => _match._regex is null ? Group.s_emptyGroup : GetGroup(_match._regex.GroupNumberFromName(groupname)); /// <summary>Provides an enumerator that iterates through the collection.</summary> /// <returns> /// An enumerator that contains all <see cref="Group" /> objects in the /// <see cref="GroupCollection" />. /// </returns> /// <remarks> /// Instead of calling the <see cref="GetEnumerator" /> method to retrieve an enumerator that lets /// you iterate through the <see cref="Group" /> objects in the collection, you should use the /// collection iteration construct provided by your programming language (such as <c>foreach</c> /// in C#). /// </remarks> 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]; } /// <summary> /// Gets a value that indicates whether access to the <see cref="GroupCollection" /> is /// synchronized (thread-safe). /// </summary> /// <value><see langword="false" /> in all cases.</value> public bool IsSynchronized => false; /// <summary> /// Gets an object that can be used to synchronize access to the /// <see cref="GroupCollection" />. /// </summary> /// <value> /// A copy of the <see cref="Match" /> object to synchronize. /// </value> public object SyncRoot => _match; /// <summary> /// Copies all the elements of the collection to the given array beginning at the given index. /// </summary> /// <param name="array">The array the collection is to be copied into.</param> /// <param name="arrayIndex">The position in the destination array where copying is to begin.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="array" /> is <see langword="null" />. /// </exception> /// <exception cref="IndexOutOfRangeException"> /// <paramref name="arrayIndex" /> is outside the bounds of <paramref name="array" />. /// -or- /// <paramref name="arrayIndex" /> plus <see cref="Count" /> is outside the bounds of /// <paramref name="array" />. /// </exception> 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); } } /// <summary> /// Copies the elements of the group collection to a <see cref="Group" /> array, starting at a /// particular array index. /// </summary> /// <param name="array"> /// The one-dimensional array that is the destination of the elements copied from the group /// collection. The array must have zero-based indexing. /// </param> /// <param name="arrayIndex"> /// The zero-based index in <paramref name="array" /> at which copying begins. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="array" /> is <see langword="null" />. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="arrayIndex" /> is less than zero. /// -or- /// <paramref name="arrayIndex" /> is greater than the length of <paramref name="array" />. /// </exception> /// <exception cref="ArgumentException"> /// The length of <paramref name="array" /> minus <paramref name="arrayIndex" /> is less than the /// group collection count. /// </exception> 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); /// <summary> /// Attempts to retrieve a group identified by the provided name key, if it exists in the group /// collection. /// </summary> /// <param name="key">A string with the group name key to look for.</param> /// <param name="value"> /// When the method returns, the group whose name is <paramref name="key" />, if it is found; /// otherwise, <see langword="null" /> if not found. /// </param> /// <returns> /// <see langword="true" /> if a group identified by the provided name key exists; /// <see langword="false" /> otherwise. /// </returns> 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; } /// <summary> /// Determines whether the group collection contains a captured group identified by the specified /// name. /// </summary> /// <param name="key">A string with the name of the captured group to locate.</param> /// <returns> /// <see langword="true" /> if the group collection contains a captured group identified by /// <paramref name="key" />; <see langword="false" /> otherwise. /// </returns> public bool ContainsKey(string key) => _match._regex!.GroupNumberFromName(key) >= 0; /// <summary>Gets a string enumeration that contains the name keys of the group collection.</summary> /// <value>The name keys of the group collection.</value> public IEnumerable<string> Keys { get { for (int i = 0; i < Count; ++i) { yield return GetGroup(i).Name; } } } /// <summary>Gets a group enumeration with all the groups in the group collection.</summary> /// <value>A group enumeration.</value> 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() { } } } }