diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
index 79efda4894eae..eafefa6e9fa33 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
@@ -74,7 +74,6 @@ System.Diagnostics.DiagnosticSource
-
@@ -93,6 +92,7 @@ System.Diagnostics.DiagnosticSource
+
@@ -105,6 +105,7 @@ System.Diagnostics.DiagnosticSource
+
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netcore.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netcore.cs
index 2a862fcb62578..839484af1cf35 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netcore.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/Instrument.netcore.cs
@@ -47,16 +47,7 @@ protected void RecordMeasurement(T measurement, KeyValuePair ta
///
/// The measurement value.
/// A of tags associated with the measurement.
- protected void RecordMeasurement(T measurement, in TagList tagList)
- {
- KeyValuePair[]? tags = tagList.Tags;
- if (tags is not null)
- {
- RecordMeasurement(measurement, tags.AsSpan(0, tagList.Count));
- return;
- }
-
- RecordMeasurement(measurement, MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in tagList.Tag1), tagList.Count));
- }
+ protected void RecordMeasurement(T measurement, in TagList tagList) =>
+ RecordMeasurement(measurement, tagList.Tags);
}
}
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/TagList.netcore.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/TagList.netcore.cs
new file mode 100644
index 0000000000000..6da234ec4ea68
--- /dev/null
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/TagList.netcore.cs
@@ -0,0 +1,340 @@
+// 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;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System.Diagnostics
+{
+ ///
+ /// Represents a list of tags that can be accessed by index. Provides methods to search, sort, and manipulate lists.
+ ///
+ ///
+ /// TagList can be used in the scenarios which need to optimize for memory allocations. TagList will avoid allocating any memory when using up to eight tags.
+ /// Using more than eight tags will cause allocating memory to store the tags.
+ /// Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct TagList : IList>, IReadOnlyList>
+ {
+ private const int OverflowAdditionalCapacity = 8;
+
+ // Up to eight tags are stored in an inline array. Once there are more items than will fit in the inline array,
+ // an array is allocated to store all the items and the inline array is abandoned. Even if the size shrinks down
+ // to below eight items, the array continues to be used.
+
+ private InlineTags _tags;
+ private KeyValuePair[]? _overflowTags;
+ private int _tagsCount;
+
+ ///
+ /// Initializes a new instance of the TagList structure using the specified .
+ ///
+ /// A span of tags to initialize the list with.
+ public TagList(params ReadOnlySpan> tagList) : this()
+ {
+ _tagsCount = tagList.Length;
+
+ scoped Span> tags = _tagsCount <= InlineTags.Length ?
+ _tags :
+ _overflowTags = new KeyValuePair[_tagsCount + OverflowAdditionalCapacity];
+
+ tagList.CopyTo(tags);
+ }
+
+ ///
+ /// Gets the number of tags contained in the .
+ ///
+ public readonly int Count => _tagsCount;
+
+ ///
+ /// Gets a value indicating whether the is read-only. This property will always return .
+ ///
+ public readonly bool IsReadOnly => false;
+
+ ///
+ /// Gets or sets the tags at the specified index.
+ ///
+ /// is not a valid index in the .
+ public KeyValuePair this[int index]
+ {
+ readonly get
+ {
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)_tagsCount, nameof(index));
+
+ return _overflowTags is null ? _tags[index] : _overflowTags[index];
+ }
+
+ set
+ {
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)_tagsCount, nameof(index));
+
+ if (_overflowTags is null)
+ {
+ _tags[index] = value;
+ }
+ else
+ {
+ _overflowTags[index] = value;
+ }
+ }
+ }
+
+ ///
+ /// Adds a tag with the provided and to the list.
+ ///
+ /// The tag key.
+ /// The tag value.
+ public void Add(string key, object? value) =>
+ Add(new KeyValuePair(key, value));
+
+ ///
+ /// Adds a tag to the list.
+ ///
+ /// Key and value pair of the tag to add to the list.
+ public void Add(KeyValuePair tag)
+ {
+ int count = _tagsCount;
+ if (_overflowTags is null && (uint)count < InlineTags.Length)
+ {
+ _tags[count] = tag;
+ _tagsCount++;
+ }
+ else
+ {
+ AddToOverflow(tag);
+ }
+ }
+
+ ///
+ /// Adds a tag to the overflow list. Slow path outlined from Add to maximize the chance for the fast path to be inlined.
+ ///
+ /// Key and value pair of the tag to add to the list.
+ private void AddToOverflow(KeyValuePair tag)
+ {
+ Debug.Assert(_overflowTags is not null || _tagsCount == InlineTags.Length);
+
+ if (_overflowTags is null)
+ {
+ _overflowTags = new KeyValuePair[InlineTags.Length + OverflowAdditionalCapacity];
+ ((ReadOnlySpan>)_tags).CopyTo(_overflowTags);
+ }
+ else if (_tagsCount == _overflowTags.Length)
+ {
+ Array.Resize(ref _overflowTags, _tagsCount + OverflowAdditionalCapacity);
+ }
+
+ _overflowTags[_tagsCount] = tag;
+ _tagsCount++;
+ }
+
+ ///
+ /// Copies the contents of this into a destination span.
+ /// Inserts an element into this at the specified index.
+ ///
+ /// The destination object.
+ /// The number of elements in the source is greater than the number of elements that the destination span.
+ public readonly void CopyTo(Span> tags)
+ {
+ if (tags.Length < _tagsCount)
+ {
+ throw new ArgumentException(SR.Arg_BufferTooSmall);
+ }
+
+ Tags.CopyTo(tags);
+ }
+
+ ///
+ /// Copies the entire to a compatible one-dimensional array, starting at the specified index of the target array.
+ ///
+ /// The one-dimensional Array that is the destination of the elements copied from . The Array must have zero-based indexing.
+ /// The zero-based index in at which copying begins.
+ /// is null.
+ /// is less than 0 or greater that or equal the length.
+ public readonly void CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ ArgumentNullException.ThrowIfNull(array);
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)arrayIndex, (uint)array.Length, nameof(arrayIndex));
+
+ CopyTo(array.AsSpan(arrayIndex));
+ }
+
+ ///
+ /// Inserts an element into the at the specified index.
+ ///
+ /// The zero-based index at which item should be inserted.
+ /// The tag to insert.
+ /// index is less than 0 or is greater than .
+ public void Insert(int index, KeyValuePair item)
+ {
+ if (index == _tagsCount)
+ {
+ Add(item);
+ return;
+ }
+
+ ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)index, (uint)_tagsCount, nameof(index));
+
+ if (_tagsCount == InlineTags.Length && _overflowTags is null)
+ {
+ _overflowTags = new KeyValuePair[InlineTags.Length + OverflowAdditionalCapacity];
+ ((ReadOnlySpan>)_tags).CopyTo(_overflowTags);
+ }
+
+ if (_overflowTags is not null)
+ {
+ if (_tagsCount == _overflowTags.Length)
+ {
+ Array.Resize(ref _overflowTags, _tagsCount + OverflowAdditionalCapacity);
+ }
+
+ _overflowTags.AsSpan(index, _tagsCount - index).CopyTo(_overflowTags.AsSpan(index + 1));
+ _overflowTags[index] = item;
+ }
+ else
+ {
+ Span> tags = _tags;
+ tags.Slice(index, _tagsCount - index).CopyTo(tags.Slice(index + 1));
+ tags[index] = item;
+ }
+
+ _tagsCount++;
+ }
+
+ ///
+ /// Removes the element at the specified index of the .
+ ///
+ /// The zero-based index of the element to remove.
+ /// index is less than 0 or is greater than .
+ public void RemoveAt(int index)
+ {
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)_tagsCount, nameof(index));
+
+ Span> tags = _overflowTags is not null ? _overflowTags : _tags;
+ tags.Slice(index + 1, _tagsCount - index - 1).CopyTo(tags.Slice(index));
+ _tagsCount--;
+ }
+
+ ///
+ /// Removes all elements from the .
+ ///
+ public void Clear() =>
+ _tagsCount = 0;
+
+ ///
+ /// Determines whether an tag is in the .
+ ///
+ /// The tag to locate in the .
+ /// if item is found in the ; otherwise, .
+ public readonly bool Contains(KeyValuePair item) =>
+ IndexOf(item) >= 0;
+
+ ///
+ /// Removes the first occurrence of a specific object from the .
+ ///
+ /// The tag to remove from the .
+ /// if item is successfully removed; otherwise, . This method also returns if item was not found in the .
+ public bool Remove(KeyValuePair item)
+ {
+ int index = IndexOf(item);
+ if (index >= 0)
+ {
+ RemoveAt(index);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the .
+ ///
+ /// Returns an enumerator that iterates through the .
+ public readonly IEnumerator> GetEnumerator() => new Enumerator(in this);
+
+ ///
+ /// Returns an enumerator that iterates through the .
+ ///
+ /// Returns an enumerator that iterates through the .
+ readonly IEnumerator IEnumerable.GetEnumerator() => new Enumerator(in this);
+
+ ///
+ /// Searches for the specified tag and returns the zero-based index of the first occurrence within the entire .
+ ///
+ /// The tag to locate in the .
+ public readonly int IndexOf(KeyValuePair item)
+ {
+ ReadOnlySpan> tags =
+ _overflowTags is not null ? _overflowTags :
+ _tags;
+
+ tags = tags.Slice(0, _tagsCount);
+
+ if (item.Value is not null)
+ {
+ for (int i = 0; i < tags.Length; i++)
+ {
+ if (item.Key == tags[i].Key && item.Value.Equals(tags[i].Value))
+ {
+ return i;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < tags.Length; i++)
+ {
+ if (item.Key == tags[i].Key && tags[i].Value is null)
+ {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ [UnscopedRef]
+ internal readonly ReadOnlySpan> Tags =>
+ _overflowTags is not null ? _overflowTags.AsSpan(0, _tagsCount) :
+ ((ReadOnlySpan>)_tags).Slice(0, _tagsCount);
+
+ [InlineArray(8)]
+ private struct InlineTags
+ {
+ public const int Length = 8;
+ private KeyValuePair _first;
+ }
+
+ public struct Enumerator : IEnumerator>
+ {
+ private TagList _tagList;
+ private int _index;
+
+ internal Enumerator(in TagList tagList)
+ {
+ _index = -1;
+ _tagList = tagList;
+ }
+
+ public KeyValuePair Current => _tagList[_index];
+
+ object IEnumerator.Current => _tagList[_index];
+
+ public void Dispose() { _index = _tagList.Count; }
+
+ public bool MoveNext()
+ {
+ _index++;
+ return _index < _tagList.Count;
+ }
+
+ public void Reset() => _index = -1;
+ }
+ }
+}
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/TagList.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/TagList.netfx.cs
similarity index 100%
rename from src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/TagList.cs
rename to src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/TagList.netfx.cs