From b4442452b5064058cb2daace0e700709eb84ef28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joonatan=20Uusv=C3=A4li?= Date: Thu, 26 Mar 2026 11:47:34 +0200 Subject: [PATCH] Introduced relevant .NET standard interfaces. Replaced `Max` with `MaxValue` and introduced `MinValue` for as required by standard `IMinMaxValue` interface. Marked `Max` as obsolete. Synced README and PACKAGE files with updates. Added and adjusted unit tests. --- README.md | 80 ++++++++++--------- .../Ulid.Boundaries.Tests.cs | 16 +++- src/ByteAether.Ulid/PACKAGE.md | 60 +++++++------- src/ByteAether.Ulid/Ulid.Boundaries.cs | 27 +++++-- src/ByteAether.Ulid/Ulid.Comparable.cs | 6 ++ src/ByteAether.Ulid/Ulid.Equatable.cs | 14 +++- src/ByteAether.Ulid/Ulid.Obsolete.cs | 10 +++ src/ByteAether.Ulid/Ulid.String.cs | 2 + 8 files changed, 140 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 96ce89f..9040e0f 100644 --- a/README.md +++ b/README.md @@ -168,81 +168,85 @@ The `Ulid` implementation provides the following properties and methods: ### Creation - `Ulid.New(GenerationOptions? options = null)`\ -Generates a new ULID using default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. + Generates a new ULID using default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. - `Ulid.New(DateTimeOffset dateTimeOffset, GenerationOptions? options = null)`\ -Generates a new ULID using the specified `DateTimeOffset` and default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. + Generates a new ULID using the specified `DateTimeOffset` and default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. - `Ulid.New(long timestamp, GenerationOptions? options = null)`\ -Generates a new ULID using the specified Unix timestamp in milliseconds (`long`) and default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. + Generates a new ULID using the specified Unix timestamp in milliseconds (`long`) and default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. - `Ulid.New(DateTimeOffset dateTimeOffset, ReadOnlySpan random)`\ -Generates a new ULID using the specified `DateTimeOffset` and a pre-existing random byte array. + Generates a new ULID using the specified `DateTimeOffset` and a pre-existing random byte array. - `Ulid.New(long timestamp, ReadOnlySpan random)`\ -Generates a new ULID using the specified Unix timestamp in milliseconds (`long`) and a pre-existing random byte array. + Generates a new ULID using the specified Unix timestamp in milliseconds (`long`) and a pre-existing random byte array. - `Ulid.New(ReadOnlySpan bytes)`\ -Creates a ULID from an existing byte array. + Creates a ULID from an existing byte array. - `Ulid.New(Guid guid)`\ -Create from existing `Guid`. + Create from existing `Guid`. - `Ulid.MinAt(DateTimeOffset datetime)`\ -Creates the minimum possible ULID value for the specified `DateTimeOffset`. + Creates the minimum possible ULID value for the specified `DateTimeOffset`. - `Ulid.MinAt(long timestamp)`\ -Creates the minimum possible ULID value for the specified Unix timestamp in milliseconds (`long`). + Creates the minimum possible ULID value for the specified Unix timestamp in milliseconds (`long`). - `Ulid.MaxAt(DateTimeOffset datetime)`\ -Creates the maximum possible ULID value for the specified `DateTimeOffset`. + Creates the maximum possible ULID value for the specified `DateTimeOffset`. - `Ulid.MaxAt(long timestamp)`\ -Creates the maximum possible ULID value for the specified Unix timestamp in milliseconds (`long`). + Creates the maximum possible ULID value for the specified Unix timestamp in milliseconds (`long`). ### Checking Validity - `Ulid.IsValid(string ulidString)`\ -Validates if the given string is a valid ULID. + Validates if the given string is a valid ULID. - `Ulid.IsValid(ReadOnlySpan ulidString)`\ -Validates if the given span of characters is a valid ULID. + Validates if the given span of characters is a valid ULID. - `Ulid.IsValid(ReadOnlySpan ulidBytes)`\ -Validates if the given byte array represents a valid ULID. + Validates if the given byte array represents a valid ULID. ### Parsing - `Ulid.Parse(ReadOnlySpan chars, IFormatProvider? provider = null)`\ -Parses a ULID from a character span in canonical format. The `IFormatProvider` is ignored. + Parses a ULID from a character span in canonical format. The `IFormatProvider` is ignored. - `Ulid.TryParse(ReadOnlySpan s, IFormatProvider? provider, out Ulid result)`\ -Tries to parse a ULID from a character span in canonical format. Returns `true` if successful. + Tries to parse a ULID from a character span in canonical format. Returns `true` if successful. - `Ulid.Parse(string s, IFormatProvider? provider = null)`\ -Parses a ULID from a string in canonical format. The `IFormatProvider` is ignored. + Parses a ULID from a string in canonical format. The `IFormatProvider` is ignored. - `Ulid.TryParse(string? s, IFormatProvider? provider, out Ulid result)`\ -Tries to parse a ULID from a string in canonical format. Returns `true` if successful. + Tries to parse a ULID from a string in canonical format. Returns `true` if successful. ### Properties +- `Ulid.MinValue`\ + Represents an empty ULID, equivalent to `default(Ulid)` and `Ulid.New(new byte[16])`. +- `Ulid.MaxValue`\ + Represents the maximum possible value for a ULID (all bytes set to `0xFF`). - `Ulid.Empty`\ -Represents an empty ULID, equivalent to `default(Ulid)` and `Ulid.New(new byte[16])`. -- `Ulid.Max`\ -Represents the maximum possible value for a ULID (all bytes set to `0xFF`). + Alias for `Ulid.MinValue`. - `Ulid.DefaultGenerationOptions`\ -Default configuration for ULID generation when no options are provided by the `Ulid.New(...)` call. + Default configuration for ULID generation when no options are provided by the `Ulid.New(...)` call. - `.Time`\ -Gets the timestamp component of the ULID as a `DateTimeOffset`. + Gets the timestamp component of the ULID as a `DateTimeOffset`. - `.TimeBytes`\ -Gets the time component of the ULID as a `ReadOnlySpan`. + Gets the time component of the ULID as a `ReadOnlySpan`. - `.Random`\ -Gets the random component of the ULID as a `ReadOnlySpan`. + Gets the random component of the ULID as a `ReadOnlySpan`. -### Conversion Methods +### Conversions & Interoperability - `.AsByteSpan()`\ -Provides a `ReadOnlySpan` representing the contents of the ULID. + Provides a `ReadOnlySpan` representing the contents of the ULID. - `.ToByteArray()`\ -Converts the ULID to a byte array. + Converts the ULID to a byte array. - `.ToGuid()`\ -Converts the ULID to a `Guid`. + Converts the ULID to a `Guid`. - `.ToString(string? format = null, IFormatProvider? formatProvider = null)`\ -Converts the ULID to a canonical string representation. Format arguments are ignored. + Converts the ULID to a canonical string representation. Format arguments are ignored. +- Provides implicit operators to and from `Guid` and `string`. -### Comparison Operators +### Comparison Operators & .NET Interfaces - Supports all comparison operators:\ -`==`, `!=`, `<`, `<=`, `>`, `>=`. + `==`, `!=`, `<`, `<=`, `>`, `>=`. - Implements standard comparison and equality methods:\ -`CompareTo`, `Equals`, `GetHashCode`. -- Provides implicit operators to and from `Guid` and `string`. + `CompareTo`, `Equals`, `GetHashCode`. +- Implements the following .NET standard interfaces:\ + `IMinMaxValue`, `IEquatable`, `IIEqualityComparer`, `IComparable`, `IComparable`, `IComparisonOperators`, `IFormattable`, `IParsable`, `ISpanFormattable`, `ISpanParsable`, `IUtf8SpanFormattable`, `IUtf8SpanParsable`. ### GenerationOptions @@ -252,17 +256,17 @@ The `GenerationOptions` class provides detailed configuration for ULID generatio Controls the behavior of ULID generation when multiple identifiers are created within the same millisecond. It determines whether ULIDs are strictly increasing or allow for random ordering within that millisecond. Available options include: `NonMonotonic`, `MonotonicIncrement` (default), `MonotonicRandom1Byte`, `MonotonicRandom2Byte`, `MonotonicRandom3Byte`, `MonotonicRandom4Byte`. - `InitialRandomSource`\ -An `IRandomProvider` for generating the random bytes of a ULID. The default `CryptographicallySecureRandomProvider` ensures robust, unpredictable ULIDs using a cryptographically secure generator. + An `IRandomProvider` for generating the random bytes of a ULID. The default `CryptographicallySecureRandomProvider` ensures robust, unpredictable ULIDs using a cryptographically secure generator. - `IncrementRandomSource`\ -An `IRandomProvider` that provides randomness for monotonic random increments. The default `PseudoRandomProvider` is a faster, non-cryptographically secure source optimized for this specific purpose. + An `IRandomProvider` that provides randomness for monotonic random increments. The default `PseudoRandomProvider` is a faster, non-cryptographically secure source optimized for this specific purpose. This library comes with two default `IRandomProvider` implementations: - **`CryptographicallySecureRandomProvider`**\ -Utilizes `System.Security.Cryptography.RandomNumberGenerator` for high-quality, cryptographically secure random data. + Utilizes `System.Security.Cryptography.RandomNumberGenerator` for high-quality, cryptographically secure random data. - **`PseudoRandomProvider`**\ -A faster, non-cryptographically secure option based on `System.Random`, ideal for performance-critical scenarios where cryptographic security is not required for random increments. + A faster, non-cryptographically secure option based on `System.Random`, ideal for performance-critical scenarios where cryptographic security is not required for random increments. Custom `IRandomProvider` implementations can also be created. diff --git a/src/ByteAether.Ulid.Tests/Ulid.Boundaries.Tests.cs b/src/ByteAether.Ulid.Tests/Ulid.Boundaries.Tests.cs index f93ce6a..6d546b0 100644 --- a/src/ByteAether.Ulid.Tests/Ulid.Boundaries.Tests.cs +++ b/src/ByteAether.Ulid.Tests/Ulid.Boundaries.Tests.cs @@ -15,10 +15,22 @@ public void EmptyUlid_ShouldBeDefault() } [Fact] - public void MaxUlid_ShouldHaveAllBytesSetToMax() + public void MinValue_ShouldBeDefault() { // Arrange - var ulid = Ulid.Max; + var ulid = Ulid.MinValue; + var emptyBytes = new byte[16]; + + // Assert + Assert.Equal(default, ulid); + Assert.Equal(emptyBytes, ulid.AsByteSpan()); + } + + [Fact] + public void MaxValue_ShouldHaveAllBytesSetToMax() + { + // Arrange + var ulid = Ulid.MaxValue; var expected = Enumerable.Repeat((byte)0xFF, 16).ToArray(); // Assert diff --git a/src/ByteAether.Ulid/PACKAGE.md b/src/ByteAether.Ulid/PACKAGE.md index a9bc75f..0070c8e 100644 --- a/src/ByteAether.Ulid/PACKAGE.md +++ b/src/ByteAether.Ulid/PACKAGE.md @@ -74,64 +74,66 @@ The `Ulid` implementation provides the following properties and methods: ### Creation - `Ulid.New(GenerationOptions? options = null)`\ -Generates a new ULID using default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. + Generates a new ULID using default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. - `Ulid.New(DateTimeOffset dateTimeOffset, GenerationOptions? options = null)`\ -Generates a new ULID using the specified `DateTimeOffset` and default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. + Generates a new ULID using the specified `DateTimeOffset` and default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. - `Ulid.New(long timestamp, GenerationOptions? options = null)`\ -Generates a new ULID using the specified Unix timestamp in milliseconds (`long`) and default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. + Generates a new ULID using the specified Unix timestamp in milliseconds (`long`) and default generation options. Accepts an optional `GenerationOptions` parameter to customize the generation behavior. - `Ulid.New(DateTimeOffset dateTimeOffset, ReadOnlySpan random)`\ -Generates a new ULID using the specified `DateTimeOffset` and a pre-existing random byte array. + Generates a new ULID using the specified `DateTimeOffset` and a pre-existing random byte array. - `Ulid.New(long timestamp, ReadOnlySpan random)`\ -Generates a new ULID using the specified Unix timestamp in milliseconds (`long`) and a pre-existing random byte array. + Generates a new ULID using the specified Unix timestamp in milliseconds (`long`) and a pre-existing random byte array. - `Ulid.New(ReadOnlySpan bytes)`\ -Creates a ULID from an existing byte array. + Creates a ULID from an existing byte array. - `Ulid.New(Guid guid)`\ -Create from existing `Guid`. + Create from existing `Guid`. - `Ulid.MinAt(DateTimeOffset datetime)`\ -Creates the minimum possible ULID value for the specified `DateTimeOffset`. + Creates the minimum possible ULID value for the specified `DateTimeOffset`. - `Ulid.MinAt(long timestamp)`\ -Creates the minimum possible ULID value for the specified Unix timestamp in milliseconds (`long`). + Creates the minimum possible ULID value for the specified Unix timestamp in milliseconds (`long`). - `Ulid.MaxAt(DateTimeOffset datetime)`\ -Creates the maximum possible ULID value for the specified `DateTimeOffset`. + Creates the maximum possible ULID value for the specified `DateTimeOffset`. - `Ulid.MaxAt(long timestamp)`\ -Creates the maximum possible ULID value for the specified Unix timestamp in milliseconds (`long`). + Creates the maximum possible ULID value for the specified Unix timestamp in milliseconds (`long`). ### Checking Validity - `Ulid.IsValid(string ulidString)`\ -Validates if the given string is a valid ULID. + Validates if the given string is a valid ULID. - `Ulid.IsValid(ReadOnlySpan ulidString)`\ -Validates if the given span of characters is a valid ULID. + Validates if the given span of characters is a valid ULID. - `Ulid.IsValid(ReadOnlySpan ulidBytes)`\ -Validates if the given byte array represents a valid ULID. + Validates if the given byte array represents a valid ULID. ### Parsing - `Ulid.Parse(ReadOnlySpan chars, IFormatProvider? provider = null)`\ -Parses a ULID from a character span in canonical format. The `IFormatProvider` is ignored. + Parses a ULID from a character span in canonical format. The `IFormatProvider` is ignored. - `Ulid.TryParse(ReadOnlySpan s, IFormatProvider? provider, out Ulid result)`\ -Tries to parse a ULID from a character span in canonical format. Returns `true` if successful. + Tries to parse a ULID from a character span in canonical format. Returns `true` if successful. - `Ulid.Parse(string s, IFormatProvider? provider = null)`\ -Parses a ULID from a string in canonical format. The `IFormatProvider` is ignored. + Parses a ULID from a string in canonical format. The `IFormatProvider` is ignored. - `Ulid.TryParse(string? s, IFormatProvider? provider, out Ulid result)`\ -Tries to parse a ULID from a string in canonical format. Returns `true` if successful. + Tries to parse a ULID from a string in canonical format. Returns `true` if successful. ### Properties +- `Ulid.MinValue`\ + Represents an empty ULID, equivalent to `default(Ulid)` and `Ulid.New(new byte[16])`. +- `Ulid.MaxValue`\ + Represents the maximum possible value for a ULID (all bytes set to `0xFF`). - `Ulid.Empty`\ -Represents an empty ULID, equivalent to `default(Ulid)` and `Ulid.New(new byte[16])`. -- `Ulid.Max`\ -Represents the maximum possible value for a ULID (all bytes set to `0xFF`). + Alias for `Ulid.MinValue`. - `Ulid.DefaultGenerationOptions`\ -Default configuration for ULID generation when no options are provided by the `Ulid.New(...)` call. + Default configuration for ULID generation when no options are provided by the `Ulid.New(...)` call. - `.Time`\ -Gets the timestamp component of the ULID as a `DateTimeOffset`. + Gets the timestamp component of the ULID as a `DateTimeOffset`. - `.TimeBytes`\ -Gets the time component of the ULID as a `ReadOnlySpan`. + Gets the time component of the ULID as a `ReadOnlySpan`. - `.Random`\ -Gets the random component of the ULID as a `ReadOnlySpan`. + Gets the random component of the ULID as a `ReadOnlySpan`. -### Conversion Methods +### Conversions & Interoperability - `.AsByteSpan()`\ Provides a `ReadOnlySpan` representing the contents of the ULID. @@ -141,14 +143,16 @@ Gets the random component of the ULID as a `ReadOnlySpan`. Converts the ULID to a `Guid`. - `.ToString(string? format = null, IFormatProvider? formatProvider = null)`\ Converts the ULID to a canonical string representation. Format arguments are ignored. +- Provides implicit operators to and from `Guid` and `string`. -### Comparison Operators +### Comparison Operators & .NET Interfaces - Supports all comparison operators:\ `==`, `!=`, `<`, `<=`, `>`, `>=`. - Implements standard comparison and equality methods:\ `CompareTo`, `Equals`, `GetHashCode`. -- Provides implicit operators to and from `Guid` and `string`. +- Implements the following .NET standard interfaces:\ + `IMinMaxValue`, `IEquatable`, `IIEqualityComparer`, `IComparable`, `IComparable`, `IComparisonOperators`, `IFormattable`, `IParsable`, `ISpanFormattable`, `ISpanParsable`, `IUtf8SpanFormattable`, `IUtf8SpanParsable`. ### GenerationOptions diff --git a/src/ByteAether.Ulid/Ulid.Boundaries.cs b/src/ByteAether.Ulid/Ulid.Boundaries.cs index a2eefac..86acc4f 100644 --- a/src/ByteAether.Ulid/Ulid.Boundaries.cs +++ b/src/ByteAether.Ulid/Ulid.Boundaries.cs @@ -1,18 +1,23 @@ +#if NET7_0_OR_GREATER +using System.Numerics; +#endif + namespace ByteAether.Ulid; public readonly partial struct Ulid +#if NET7_0_OR_GREATER + : IMinMaxValue +#endif { private static readonly byte[] _randomMin = Enumerable.Repeat((byte)0x00, _ulidSizeRandom).ToArray(); private static readonly byte[] _randomMax = Enumerable.Repeat((byte)0xFF, _ulidSizeRandom).ToArray(); - /// - /// Represents an empty ULID value. - /// + /// Gets the minimum value of the ULID type. /// - /// The field is a ULID with all components set to zero. + /// The field is a ULID with all components set to zero. /// It can be used as a default or placeholder value. /// - public static readonly Ulid Empty = default; + public static Ulid MinValue { get; } = default; /// /// Represents the maximum possible value for a ULID. @@ -21,7 +26,17 @@ public readonly partial struct Ulid /// The field is a ULID where all byte components are set to their highest possible value (0xFF). /// It can be used as a sentinel or boundary value in comparison operations or range validations. /// - public static readonly Ulid Max = New(Enumerable.Repeat((byte)0xFF, _ulidSize).ToArray()); + public static Ulid MaxValue { get; } = New(Enumerable.Repeat((byte)0xFF, _ulidSize).ToArray()); + + /// + /// Represents an empty ULID value. + /// + /// + /// The field is a ULID with all components set to zero. + /// It can be used as a default or placeholder value. + /// It is equivalent to , but is provided for clarity. + /// + public static Ulid Empty => MinValue; /// /// Creates the minimum possible value for the specified timestamp. diff --git a/src/ByteAether.Ulid/Ulid.Comparable.cs b/src/ByteAether.Ulid/Ulid.Comparable.cs index 7319872..edc381f 100644 --- a/src/ByteAether.Ulid/Ulid.Comparable.cs +++ b/src/ByteAether.Ulid/Ulid.Comparable.cs @@ -1,7 +1,13 @@ using System.Runtime.CompilerServices; +#if NET7_0_OR_GREATER +using System.Numerics; +#endif namespace ByteAether.Ulid; public readonly partial struct Ulid : IComparable, IComparable +#if NET7_0_OR_GREATER + , IComparisonOperators +#endif { /// /// Determines whether the value of the left ULID is less than the value of the right ULID. diff --git a/src/ByteAether.Ulid/Ulid.Equatable.cs b/src/ByteAether.Ulid/Ulid.Equatable.cs index b8c7048..f943586 100644 --- a/src/ByteAether.Ulid/Ulid.Equatable.cs +++ b/src/ByteAether.Ulid/Ulid.Equatable.cs @@ -4,11 +4,23 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; #endif +#if NET7_0_OR_GREATER +using System.Numerics; +#endif namespace ByteAether.Ulid; -public readonly partial struct Ulid : IEquatable +public readonly partial struct Ulid : IEquatable, IEqualityComparer +#if NET7_0_OR_GREATER + , IEqualityOperators // Keeping this here for clarity +#endif { + /// + public int GetHashCode(Ulid ulid) => ulid.GetHashCode(); + + /// + public bool Equals(Ulid x, Ulid y) => EqualsCore(x, y); + /// #if NET5_0_OR_GREATER [SkipLocalsInit] diff --git a/src/ByteAether.Ulid/Ulid.Obsolete.cs b/src/ByteAether.Ulid/Ulid.Obsolete.cs index c91513c..a873b64 100644 --- a/src/ByteAether.Ulid/Ulid.Obsolete.cs +++ b/src/ByteAether.Ulid/Ulid.Obsolete.cs @@ -5,6 +5,16 @@ namespace ByteAether.Ulid; public readonly partial struct Ulid { + /// + /// Represents the maximum possible value for a ULID. + /// + /// + /// The field is a ULID where all byte components are set to their highest possible value (0xFF). + /// It can be used as a sentinel or boundary value in comparison operations or range validations. + /// + [Obsolete("Use MaxValue instead.")] + public static Ulid Max => MaxValue; + /// /// Whether s should be generated in a monotonic manner by default.
/// Initial value is set to true.
diff --git a/src/ByteAether.Ulid/Ulid.String.cs b/src/ByteAether.Ulid/Ulid.String.cs index 672c124..f03f291 100644 --- a/src/ByteAether.Ulid/Ulid.String.cs +++ b/src/ByteAether.Ulid/Ulid.String.cs @@ -11,9 +11,11 @@ public readonly partial struct Ulid #if NET6_0_OR_GREATER , ISpanFormattable #if NET7_0_OR_GREATER + , IParsable // Keeping this here for clarity , ISpanParsable #if NET8_0_OR_GREATER , IUtf8SpanFormattable + , IUtf8SpanParsable #endif #endif #endif