diff --git a/Directory.Packages.props b/Directory.Packages.props index c84a6f7..baba2e6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,6 +9,5 @@ - \ No newline at end of file diff --git a/README.md b/README.md index d849c0b..815cac3 100644 --- a/README.md +++ b/README.md @@ -47,16 +47,6 @@ foreach (ref readonly HeavyStruct s in view) } ``` -## Changelog - -### 1.2.2 -- Optimize `ArrayView.Create` method for empty collection expression - -### 1.2.1 -- Add `List` to `ArrayView` extension for NET9.0+ - -### 1.2.0 -- Add `Trim` overloads to `StringView` class ## Supported versions diff --git a/Ramstack.Structures.Tests/Collections/ArrayViewExtensionsTests.cs b/Ramstack.Structures.Tests/Collections/ArrayViewExtensionsTests.cs index 07652b1..76c0501 100644 --- a/Ramstack.Structures.Tests/Collections/ArrayViewExtensionsTests.cs +++ b/Ramstack.Structures.Tests/Collections/ArrayViewExtensionsTests.cs @@ -1,10 +1,10 @@ namespace Ramstack.Collections; +#if NET9_0_OR_GREATER + [TestFixture] public class ArrayViewExtensionsTests { - #if NET9_0_OR_GREATER - [Test] public void List_AsView_Empty() { @@ -25,5 +25,6 @@ public void List_AsView() Assert.That(view, Is.EquivalentTo(list)); } - #endif } + +#endif diff --git a/Ramstack.Structures/Collections/ArrayViewExtensions.cs b/Ramstack.Structures/Collections/ArrayViewExtensions.cs index 8b84906..d1f59ac 100644 --- a/Ramstack.Structures/Collections/ArrayViewExtensions.cs +++ b/Ramstack.Structures/Collections/ArrayViewExtensions.cs @@ -149,9 +149,13 @@ public static ArrayView AsView(this List? list) if (list is not null) { var array = ListAccessor.GetArray(list); - var count = Math.Min(list.Count, array.Length); + _ = array.Length; - return new ArrayView(array, 0, count); + // + // SCG.List maintains internal invariants, so we can safely use the unchecked constructor + // to bypass redundant bounds checks for better performance. + // + return new ArrayView(array, 0, list.Count, unused: 0); } return ArrayView.Empty; diff --git a/Ramstack.Structures/Collections/ArrayView`1.cs b/Ramstack.Structures/Collections/ArrayView`1.cs index 56e6a04..83414dd 100644 --- a/Ramstack.Structures/Collections/ArrayView`1.cs +++ b/Ramstack.Structures/Collections/ArrayView`1.cs @@ -101,17 +101,23 @@ public ArrayView(T[] array, int index, int length) /// Initializes a new instance of the structure that creates /// a view for the specified range of the elements in the specified array. /// + /// + /// This constructor is intentionally minimal and skips all argument validations, + /// as the caller is responsible for ensuring correctness (e.g., + /// is non-null, and + /// are within bounds). + /// /// The array to wrap. /// The zero-based index of the first element in the range. /// The number of elements in the range. - /// The dummy parameter. + /// Unused parameter, exists solely to disambiguate overloads. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ArrayView(T[] array, int index, int length, int dummy) + internal ArrayView(T[] array, int index, int length, int unused) { _index = index; _count = length; _array = array; - _ = dummy; + _ = unused; } /// @@ -132,7 +138,7 @@ public ArrayView Slice(int index) if ((uint)index > (uint)_count) ThrowHelper.ThrowArgumentOutOfRangeException(); - return new ArrayView(_array!, _index + index, _count - index, dummy: 0); + return new ArrayView(_array!, _index + index, _count - index, unused: 0); } /// @@ -157,7 +163,7 @@ public ArrayView Slice(int index, int count) ThrowHelper.ThrowArgumentOutOfRangeException(); } - return new ArrayView(_array!, _index + index, count, dummy: 0); + return new ArrayView(_array!, _index + index, count, unused: 0); } /// @@ -316,7 +322,7 @@ ref array.GetRawArrayData(view._index), /// A representation of the array segment. /// public static implicit operator ArrayView(ArraySegment segment) => - new(segment.Array!, segment.Offset, segment.Count, dummy: 0); + new(segment.Array!, segment.Offset, segment.Count, unused: 0); /// /// Returns a string representation of the current instance's state, diff --git a/Ramstack.Structures/Internal/JitHelpers.cs b/Ramstack.Structures/Internal/JitHelpers.cs index 7fc3c8f..f664a93 100644 --- a/Ramstack.Structures/Internal/JitHelpers.cs +++ b/Ramstack.Structures/Internal/JitHelpers.cs @@ -30,7 +30,7 @@ public static ref readonly char GetRawStringData(this string text, int index) => public static ref T GetRawArrayData(this T[] array, int index) { // It's valid for a ref to point just past the end of an array, and it'll - // be properly GC-tracked. (Though dereferencing it may result in undefined behavior.) + // be properly GC-tracked. (Though dereferencing it may result in undefined behavior) return ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), (nint)(uint)index); } } diff --git a/Ramstack.Structures/Ramstack.Structures.csproj b/Ramstack.Structures/Ramstack.Structures.csproj index e674105..b74071e 100644 --- a/Ramstack.Structures/Ramstack.Structures.csproj +++ b/Ramstack.Structures/Ramstack.Structures.csproj @@ -64,11 +64,6 @@ Ramstack.Collections.ReadOnlyArray<T> - - - - - True diff --git a/Ramstack.Structures/Text/StringView.cs b/Ramstack.Structures/Text/StringView.cs index 786c7cb..a2a3682 100644 --- a/Ramstack.Structures/Text/StringView.cs +++ b/Ramstack.Structures/Text/StringView.cs @@ -45,7 +45,7 @@ public char this[int index] /// /// The string to wrap. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public StringView(string value) : this(value, 0, value.Length, dummy: 0) + public StringView(string value) : this(value, 0, value.Length, unused: 0) { } @@ -96,17 +96,23 @@ public StringView(string value, int index, int length) /// Initializes a new instance of the structure that creates /// a view for the specified range of the characters in the specified string. /// + /// + /// This constructor is intentionally minimal and skips all argument validations, + /// as the caller is responsible for ensuring correctness (e.g., + /// is non-null, and + /// are within bounds). + /// /// The string to wrap. /// The zero-based index of the first character in the range. /// The number of characters in the range. - /// The dummy parameter. + /// Unused parameter, exists solely to disambiguate overloads. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private StringView(string value, int index, int length, int dummy) + private StringView(string value, int index, int length, int unused) { _index = index; _length = length; _value = value; - _ = dummy; + _ = unused; } /// @@ -156,7 +162,7 @@ public StringView Slice(int start) if ((uint)start > (uint)_length) ThrowHelper.ThrowArgumentOutOfRangeException(); - return new StringView(_value!, _index + start, _length - start, dummy: 0); + return new StringView(_value!, _index + start, _length - start, unused: 0); } /// @@ -182,7 +188,7 @@ public StringView Slice(int start, int length) ThrowHelper.ThrowArgumentOutOfRangeException(); } - return new StringView(_value!, _index + start, length, dummy: 0); + return new StringView(_value!, _index + start, length, unused: 0); } /// @@ -256,7 +262,7 @@ public StringView TrimStart() if (!char.IsWhiteSpace(value.GetRawStringData(start))) break; - return new StringView(value!, start, final - start, dummy: 0); + return new StringView(value!, start, final - start, unused: 0); } /// @@ -277,7 +283,7 @@ public StringView TrimStart(char trimChar) if (value.GetRawStringData(start) != trimChar) break; - return new StringView(value!, start, final - start, dummy: 0); + return new StringView(value!, start, final - start, unused: 0); } /// @@ -306,7 +312,7 @@ public StringView TrimStart(params char[]? trimChars) => /// /// The trimmed . /// - public StringView TrimStart(ReadOnlySpan trimChars) + public StringView TrimStart(params ReadOnlySpan trimChars) { if (trimChars.Length == 0) return TrimStart(); @@ -319,8 +325,10 @@ public StringView TrimStart(ReadOnlySpan trimChars) { for (; start < final; start++) { - for (var i = 0; i < trimChars.Length; i++) - if (value.GetRawStringData(start) == trimChars[i]) + var ch = value.GetRawStringData(start); + + foreach (var trimChar in trimChars) + if (ch == trimChar) goto MATCHED; break; @@ -328,7 +336,7 @@ public StringView TrimStart(ReadOnlySpan trimChars) } } - return new StringView(value!, start, final - start, dummy: 0); + return new StringView(value!, start, final - start, unused: 0); } /// @@ -348,7 +356,7 @@ public StringView TrimEnd() if (!char.IsWhiteSpace(value.GetRawStringData(final))) break; - return new StringView(value!, start, final + 1 - start, dummy: 0); + return new StringView(value!, start, final + 1 - start, unused: 0); } /// @@ -369,7 +377,7 @@ public StringView TrimEnd(char trimChar) if (value.GetRawStringData(final) != trimChar) break; - return new StringView(value!, start, final + 1 - start, dummy: 0); + return new StringView(value!, start, final + 1 - start, unused: 0); } /// @@ -398,7 +406,7 @@ public StringView TrimEnd(params char[]? trimChars) => /// /// The trimmed . /// - public StringView TrimEnd(ReadOnlySpan trimChars) + public StringView TrimEnd(params ReadOnlySpan trimChars) { if (trimChars.Length == 0) return TrimEnd(); @@ -411,8 +419,10 @@ public StringView TrimEnd(ReadOnlySpan trimChars) { for (; final >= start; final--) { - for (var i = 0; i < trimChars.Length; i++) - if (value.GetRawStringData(final) == trimChars[i]) + var ch = value.GetRawStringData(final); + + foreach (var trimChar in trimChars) + if (ch == trimChar) goto MATCHED; break; @@ -420,7 +430,7 @@ public StringView TrimEnd(ReadOnlySpan trimChars) } } - return new StringView(value!, start, final + 1 - start, dummy: 0); + return new StringView(value!, start, final + 1 - start, unused: 0); } /// @@ -446,7 +456,7 @@ public StringView Trim() break; } - return new StringView(value!, start, final + 1 - start, dummy: 0); + return new StringView(value!, start, final + 1 - start, unused: 0); } /// @@ -473,7 +483,7 @@ public StringView Trim(char trimChar) break; } - return new StringView(value!, start, final + 1 - start, dummy: 0); + return new StringView(value!, start, final + 1 - start, unused: 0); } /// @@ -502,7 +512,7 @@ public StringView Trim(params char[]? trimChars) => /// /// The trimmed . /// - public StringView Trim(ReadOnlySpan trimChars) + public StringView Trim(params ReadOnlySpan trimChars) { if (trimChars.Length == 0) return Trim(); @@ -515,8 +525,10 @@ public StringView Trim(ReadOnlySpan trimChars) { for (; start <= final; start++) { - for (var i = 0; i < trimChars.Length; i++) - if (value.GetRawStringData(start) == trimChars[i]) + var ch = value.GetRawStringData(start); + + foreach (var trimChar in trimChars) + if (ch == trimChar) goto MATCHED; break; @@ -525,8 +537,10 @@ public StringView Trim(ReadOnlySpan trimChars) for (; final > start; final--) { - for (var i = 0; i < trimChars.Length; i++) - if (value.GetRawStringData(final) == trimChars[i]) + var ch = value.GetRawStringData(final); + + foreach (var trimChar in trimChars) + if (ch == trimChar) goto MATCHED; break; @@ -534,7 +548,7 @@ public StringView Trim(ReadOnlySpan trimChars) } } - return new StringView(value!, start, final + 1 - start, dummy: 0); + return new StringView(value!, start, final + 1 - start, unused: 0); } /// @@ -723,8 +737,8 @@ public override bool Equals([NotNullWhen(true)] object? obj) if (obj is null) return _length == 0; - if (obj is StringView) - return Equals(this, Unsafe.Unbox(obj)); + if (obj is StringView view) + return Equals(this, view); return obj is string s && Equals(s); }