diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 81d6268..1e8e25f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,7 +3,6 @@ on: push: branches: ["main"] - workflow_dispatch: permissions: diff --git a/src/LightResults/LightResults.csproj b/src/LightResults/LightResults.csproj index da8767a..fbc67e9 100644 --- a/src/LightResults/LightResults.csproj +++ b/src/LightResults/LightResults.csproj @@ -6,7 +6,7 @@ enable LightResults latest - 8.0.9 + 9.0.0-preview.1 LightResults Jean-Sebastien Carle An extremely light and modern Result Pattern library. @@ -19,8 +19,8 @@ https://github.com/jscarle/LightResults git result results pattern fluentresults error handling - 8.0.9.0 - 8.0.9.0 + 9.0.0.0 + 9.0.0.0 en-US true snupkg @@ -39,6 +39,7 @@ + diff --git a/src/LightResults/Result.cs b/src/LightResults/Result.cs index 9e0d463..c736134 100644 --- a/src/LightResults/Result.cs +++ b/src/LightResults/Result.cs @@ -4,7 +4,7 @@ namespace LightResults; /// Represents a result. -public sealed class Result : +public readonly struct Result : IEquatable, #if NET7_0_OR_GREATER IActionableResult #else @@ -12,13 +12,29 @@ public sealed class Result : #endif { /// - public bool IsSuccess => _errors.Length == 0; + public bool IsSuccess + { + get + { + if (_errors is null) + return true; + return _errors.Value.Length == 0; + } + } /// - public bool IsFailed => _errors.Length != 0; + public bool IsFailed + { + get + { + if (_errors is null) + return false; + return _errors.Value.Length != 0; + } + } /// - public IReadOnlyCollection Errors => _errors; + public IReadOnlyCollection Errors => _errors ?? ImmutableArray.Empty; /// public IError Error @@ -28,15 +44,16 @@ public IError Error if (IsSuccess) throw new InvalidOperationException($"{nameof(Result)} is successful. {nameof(Error)} is not set."); - return _errors[0]; + return _errors!.Value[0]; } } private static readonly Result OkResult = new(); private static readonly Result FailedResult = new(LightResults.Error.Empty); - private readonly ImmutableArray _errors; + private readonly ImmutableArray? _errors; - private Result() + /// Initializes a new instance of the struct. + public Result() { _errors = ImmutableArray.Empty; } @@ -180,13 +197,16 @@ public static Result Fail(IEnumerable errors) /// public bool HasError() where TError : IError { + if (_errors is null) + return false; + // Do not convert to LINQ, this creates unnecessary heap allocations. // For is the most efficient way to loop. It is the fastest and does not allocate. // ReSharper disable once ForCanBeConvertedToForeach // ReSharper disable once LoopCanBeConvertedToQuery - for (var index = 0; index < _errors.Length; index++) + for (var index = 0; index < _errors!.Value.Length; index++) { - var error = _errors[index]; + var error = _errors!.Value[index]; if (error is TError) return true; } @@ -200,10 +220,51 @@ public override string ToString() if (IsSuccess) return $"{nameof(Result)} {{ IsSuccess = True }}"; - if (_errors[0].Message.Length == 0) + if (_errors!.Value[0].Message.Length == 0) return $"{nameof(Result)} {{ IsSuccess = False }}"; - var errorString = StringHelper.GetResultErrorString(_errors); + var errorString = StringHelper.GetResultErrorString(_errors!.Value); return StringHelper.GetResultString(nameof(Result), "False", errorString); } -} \ No newline at end of file + + /// Determines whether two instances are equal. + /// The instance to compare with this instance. + /// true if the specified is equal to this instance; otherwise, false. + public bool Equals(Result other) + { + return Nullable.Equals(_errors, other._errors); + } + + /// Determines whether the specified object is equal to this instance. + /// The object to compare with this instance. + /// true if the specified object is equal to this instance; otherwise, false. + public override bool Equals(object? obj) + { + return obj is Result other && Equals(other); + } + + /// Returns the hash code for this instance. + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return _errors.GetHashCode(); + } + + /// Determines whether two instances are equal. + /// The first instance to compare. + /// The second instance to compare. + /// true if the specified instances are equal; otherwise, false. + public static bool operator ==(Result left, Result right) + { + return left.Equals(right); + } + + /// Determines whether two instances are not equal. + /// The first instance to compare. + /// The second instance to compare. + /// true if the specified instances are not equal; otherwise, false. + public static bool operator !=(Result left, Result right) + { + return !left.Equals(right); + } +} diff --git a/src/LightResults/Result`1.cs b/src/LightResults/Result`1.cs index 4c2e0b5..64ee92b 100644 --- a/src/LightResults/Result`1.cs +++ b/src/LightResults/Result`1.cs @@ -6,7 +6,7 @@ namespace LightResults; // ReSharper disable StaticMemberInGenericType /// Represents a result. /// The type of the value in the result. -public sealed class Result : +public readonly struct Result : IEquatable>, #if NET7_0_OR_GREATER IActionableResult> #else @@ -14,13 +14,29 @@ public sealed class Result : #endif { /// - public bool IsSuccess => _errors.Length == 0; + public bool IsSuccess + { + get + { + if (_errors is null) + return true; + return _errors.Value.Length == 0; + } + } /// - public bool IsFailed => _errors.Length != 0; + public bool IsFailed + { + get + { + if (_errors is null) + return false; + return _errors.Value.Length != 0; + } + } /// - public IReadOnlyCollection Errors => _errors; + public IReadOnlyCollection Errors => _errors ?? ImmutableArray.Empty; /// public IError Error @@ -30,7 +46,7 @@ public IError Error if (IsSuccess) throw new InvalidOperationException($"{nameof(Result)} is successful. {nameof(Error)} is not set."); - return _errors[0]; + return _errors!.Value[0]; } } @@ -48,10 +64,11 @@ public TValue Value } private static readonly Result FailedResult = new(LightResults.Error.Empty); - private readonly ImmutableArray _errors; + private readonly ImmutableArray? _errors; private readonly TValue? _valueOrDefault; - private Result() + /// Initializes a new instance of the struct. + public Result() { _errors = ImmutableArray.Empty; } @@ -133,13 +150,16 @@ public static Result Fail(IEnumerable errors) /// public bool HasError() where TError : IError { + if (_errors is null) + return false; + // Do not convert to LINQ, this creates unnecessary heap allocations. // For is the most efficient way to loop. It is the fastest and does not allocate. // ReSharper disable once ForCanBeConvertedToForeach // ReSharper disable once LoopCanBeConvertedToQuery - for (var index = 0; index < _errors.Length; index++) + for (var index = 0; index < _errors!.Value.Length; index++) { - var error = _errors[index]; + var error = _errors!.Value[index]; if (error is TError) return true; } @@ -156,10 +176,52 @@ public override string ToString() return StringHelper.GetResultString(nameof(Result), "True", valueString); } - if (_errors[0].Message.Length == 0) + if (_errors!.Value[0].Message.Length == 0) return $"{nameof(Result)} {{ IsSuccess = False }}"; - var errorString = StringHelper.GetResultErrorString(_errors); + var errorString = StringHelper.GetResultErrorString(_errors!.Value); return StringHelper.GetResultString(nameof(Result), "False", errorString); } -} \ No newline at end of file + + /// Determines whether two instances are equal. + /// The instance to compare with this instance. + /// true if the specified is equal to this instance; otherwise, false. + public bool Equals(Result other) + { + return Nullable.Equals(_errors, other._errors) && EqualityComparer.Default.Equals(_valueOrDefault, other._valueOrDefault); + } + + + /// Determines whether the specified object is equal to this instance. + /// The object to compare with this instance. + /// true if the specified object is equal to this instance; otherwise, false. + public override bool Equals(object? obj) + { + return obj is Result other && Equals(other); + } + + /// Returns the hash code for this instance. + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return HashCode.Combine(_errors, _valueOrDefault); + } + + /// Determines whether two instances are equal. + /// The first instance to compare. + /// The second instance to compare. + /// true if the specified instances are equal; otherwise, false. + public static bool operator ==(Result left, Result right) + { + return left.Equals(right); + } + + /// Determines whether two instances are not equal. + /// The first instance to compare. + /// The second instance to compare. + /// true if the specified instances are not equal; otherwise, false. + public static bool operator !=(Result left, Result right) + { + return !left.Equals(right); + } +} diff --git a/tools/LightResults.ComparisonBenchmarks/Benchmarks.cs b/tools/LightResults.ComparisonBenchmarks/Benchmarks.cs index c93fcec..394c381 100644 --- a/tools/LightResults.ComparisonBenchmarks/Benchmarks.cs +++ b/tools/LightResults.ComparisonBenchmarks/Benchmarks.cs @@ -7,7 +7,7 @@ namespace LightResults.ComparisonBenchmarks; [MemoryDiagnoser] [SimpleJob(RuntimeMoniker.Net80)] -[HideColumns(Column.Job, Column.Iterations, Column.Error, Column.StdDev, Column.RatioSD, Column.Gen0, Column.Gen1, Column.Gen2)] +[HideColumns(Column.Job, Column.Iterations, Column.Error, Column.StdDev, Column.Median, Column.RatioSD, Column.Gen0, Column.Gen1, Column.Gen2)] [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] [CategoriesColumn] public partial class Benchmarks @@ -17,4 +17,4 @@ public partial class Benchmarks private const int ResultValue = 0; private const string ErrorMessage = "An unknown error occured."; -} +} \ No newline at end of file diff --git a/tools/LightResults.CurrentBenchmarks/Benchmarks.cs b/tools/LightResults.CurrentBenchmarks/Benchmarks.cs index 7f2a1dd..5cff6c0 100644 --- a/tools/LightResults.CurrentBenchmarks/Benchmarks.cs +++ b/tools/LightResults.CurrentBenchmarks/Benchmarks.cs @@ -6,7 +6,7 @@ namespace LightResults.CurrentBenchmarks; [MemoryDiagnoser] [SimpleJob(RuntimeMoniker.Net80)] -[HideColumns(Column.Job, Column.Iterations, Column.Error, Column.StdDev, Column.Gen0, Column.Gen1, Column.Gen2)] +[HideColumns(Column.Job, Column.Iterations, Column.Error, Column.StdDev, Column.Median, Column.Gen0, Column.Gen1, Column.Gen2)] public class Benchmarks { [Params(100_000)] @@ -73,7 +73,7 @@ public void Current_Result_HasError() } [Benchmark] - public void Current_Result_ErrorsIndexer() + public void Current_Result_Error() { for (var iteration = 0; iteration < Iterations; iteration++) _ = ResultFailWithErrorMessage.Error; diff --git a/tools/LightResults.DevelopBenchmarks/Benchmarks.cs b/tools/LightResults.DevelopBenchmarks/Benchmarks.cs index a2b588d..2229889 100644 --- a/tools/LightResults.DevelopBenchmarks/Benchmarks.cs +++ b/tools/LightResults.DevelopBenchmarks/Benchmarks.cs @@ -6,7 +6,7 @@ namespace LightResults.DevelopBenchmarks; [MemoryDiagnoser] [SimpleJob(RuntimeMoniker.Net80)] -[HideColumns(Column.Job, Column.Iterations, Column.Error, Column.StdDev, Column.Gen0, Column.Gen1, Column.Gen2)] +[HideColumns(Column.Job, Column.Iterations, Column.Error, Column.StdDev, Column.Median, Column.Gen0, Column.Gen1, Column.Gen2)] public class Benchmarks { [Params(100_000)]