Skip to content

Commit

Permalink
Changed Result into struct.
Browse files Browse the repository at this point in the history
  • Loading branch information
jscarle committed Mar 5, 2024
1 parent 9163758 commit 1f01b63
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 33 deletions.
1 change: 0 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
on:
push:
branches: ["main"]

workflow_dispatch:

permissions:
Expand Down
7 changes: 4 additions & 3 deletions src/LightResults/LightResults.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<RootNamespace>LightResults</RootNamespace>
<LangVersion>latest</LangVersion>
<Version>8.0.9</Version>
<Version>9.0.0-preview.1</Version>
<Title>LightResults</Title>
<Authors>Jean-Sebastien Carle</Authors>
<Description>An extremely light and modern Result Pattern library.</Description>
Expand All @@ -19,8 +19,8 @@
<RepositoryUrl>https://github.com/jscarle/LightResults</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>result results pattern fluentresults error handling</PackageTags>
<AssemblyVersion>8.0.9.0</AssemblyVersion>
<FileVersion>8.0.9.0</FileVersion>
<AssemblyVersion>9.0.0.0</AssemblyVersion>
<FileVersion>9.0.0.0</FileVersion>
<NeutralLanguage>en-US</NeutralLanguage>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
Expand All @@ -39,6 +39,7 @@

<ItemGroup Condition="'$(targetframework)' == 'netstandard2.0'">
<PackageReference Include="System.Collections.Immutable" Version="8.0.0"/>
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" PrivateAssets="all"/>
</ItemGroup>

<ItemGroup>
Expand Down
85 changes: 73 additions & 12 deletions src/LightResults/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,37 @@
namespace LightResults;

/// <summary>Represents a result.</summary>
public sealed class Result :
public readonly struct Result : IEquatable<Result>,
#if NET7_0_OR_GREATER
IActionableResult<Result>
#else
IResult
#endif
{
/// <inheritdoc />
public bool IsSuccess => _errors.Length == 0;
public bool IsSuccess
{
get
{
if (_errors is null)
return true;
return _errors.Value.Length == 0;
}
}

/// <inheritdoc />
public bool IsFailed => _errors.Length != 0;
public bool IsFailed
{
get
{
if (_errors is null)
return false;
return _errors.Value.Length != 0;
}
}

/// <inheritdoc />
public IReadOnlyCollection<IError> Errors => _errors;
public IReadOnlyCollection<IError> Errors => _errors ?? ImmutableArray<IError>.Empty;

/// <inheritdoc />
public IError Error
Expand All @@ -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<IError> _errors;
private readonly ImmutableArray<IError>? _errors;

private Result()
/// <summary>Initializes a new instance of the <see cref="Result" /> struct.</summary>
public Result()
{
_errors = ImmutableArray<IError>.Empty;
}
Expand Down Expand Up @@ -180,13 +197,16 @@ public static Result<TValue> Fail<TValue>(IEnumerable<IError> errors)
/// <inheritdoc />
public bool HasError<TError>() 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;
}
Expand All @@ -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);
}
}

/// <summary>Determines whether two <see cref="Result" /> instances are equal.</summary>
/// <param name="other">The <see cref="Result" /> instance to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Result" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(Result other)
{
return Nullable.Equals(_errors, other._errors);
}

/// <summary>Determines whether the specified object is equal to this instance.</summary>
/// <param name="obj">The object to compare with this instance.</param>
/// <returns><c>true</c> if the specified object is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
return obj is Result other && Equals(other);
}

/// <summary>Returns the hash code for this instance.</summary>
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
{
return _errors.GetHashCode();
}

/// <summary>Determines whether two <see cref="Result" /> instances are equal.</summary>
/// <param name="left">The first <see cref="Result" /> instance to compare.</param>
/// <param name="right">The second <see cref="Result" /> instance to compare.</param>
/// <returns><c>true</c> if the specified <see cref="Result" /> instances are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(Result left, Result right)
{
return left.Equals(right);
}

/// <summary>Determines whether two <see cref="Result" /> instances are not equal.</summary>
/// <param name="left">The first <see cref="Result" /> instance to compare.</param>
/// <param name="right">The second <see cref="Result" /> instance to compare.</param>
/// <returns><c>true</c> if the specified <see cref="Result" /> instances are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(Result left, Result right)
{
return !left.Equals(right);
}
}
86 changes: 74 additions & 12 deletions src/LightResults/Result`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,37 @@ namespace LightResults;
// ReSharper disable StaticMemberInGenericType
/// <summary>Represents a result.</summary>
/// <typeparam name="TValue">The type of the value in the result.</typeparam>
public sealed class Result<TValue> :
public readonly struct Result<TValue> : IEquatable<Result<TValue>>,
#if NET7_0_OR_GREATER
IActionableResult<TValue, Result<TValue>>
#else
IResult<TValue>
#endif
{
/// <inheritdoc />
public bool IsSuccess => _errors.Length == 0;
public bool IsSuccess
{
get
{
if (_errors is null)
return true;
return _errors.Value.Length == 0;
}
}

/// <inheritdoc />
public bool IsFailed => _errors.Length != 0;
public bool IsFailed
{
get
{
if (_errors is null)
return false;
return _errors.Value.Length != 0;
}
}

/// <inheritdoc />
public IReadOnlyCollection<IError> Errors => _errors;
public IReadOnlyCollection<IError> Errors => _errors ?? ImmutableArray<IError>.Empty;

/// <inheritdoc />
public IError Error
Expand All @@ -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];
}
}

Expand All @@ -48,10 +64,11 @@ public TValue Value
}

private static readonly Result<TValue> FailedResult = new(LightResults.Error.Empty);
private readonly ImmutableArray<IError> _errors;
private readonly ImmutableArray<IError>? _errors;
private readonly TValue? _valueOrDefault;

private Result()
/// <summary>Initializes a new instance of the <see cref="Result{TValue}" /> struct.</summary>
public Result()
{
_errors = ImmutableArray<IError>.Empty;
}
Expand Down Expand Up @@ -133,13 +150,16 @@ public static Result<TValue> Fail(IEnumerable<IError> errors)
/// <inheritdoc />
public bool HasError<TError>() 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;
}
Expand All @@ -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);
}
}

/// <summary>Determines whether two <see cref="Result{TValue}" /> instances are equal.</summary>
/// <param name="other">The <see cref="Result{TValue}" /> instance to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="Result{TValue}" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(Result<TValue> other)
{
return Nullable.Equals(_errors, other._errors) && EqualityComparer<TValue?>.Default.Equals(_valueOrDefault, other._valueOrDefault);
}


/// <summary>Determines whether the specified object is equal to this instance.</summary>
/// <param name="obj">The object to compare with this instance.</param>
/// <returns><c>true</c> if the specified object is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
return obj is Result<TValue> other && Equals(other);
}

/// <summary>Returns the hash code for this instance.</summary>
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode()
{
return HashCode.Combine(_errors, _valueOrDefault);
}

/// <summary>Determines whether two <see cref="Result{TValue}" /> instances are equal.</summary>
/// <param name="left">The first <see cref="Result{TValue}" /> instance to compare.</param>
/// <param name="right">The second <see cref="Result{TValue}" /> instance to compare.</param>
/// <returns><c>true</c> if the specified <see cref="Result{TValue}" /> instances are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(Result<TValue> left, Result<TValue> right)
{
return left.Equals(right);
}

/// <summary>Determines whether two <see cref="Result{TValue}" /> instances are not equal.</summary>
/// <param name="left">The first <see cref="Result{TValue}" /> instance to compare.</param>
/// <param name="right">The second <see cref="Result{TValue}" /> instance to compare.</param>
/// <returns><c>true</c> if the specified <see cref="Result{TValue}" /> instances are not equal; otherwise, <c>false</c>.</returns>
public static bool operator !=(Result<TValue> left, Result<TValue> right)
{
return !left.Equals(right);
}
}
4 changes: 2 additions & 2 deletions tools/LightResults.ComparisonBenchmarks/Benchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,4 +17,4 @@ public partial class Benchmarks

private const int ResultValue = 0;
private const string ErrorMessage = "An unknown error occured.";
}
}
4 changes: 2 additions & 2 deletions tools/LightResults.CurrentBenchmarks/Benchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion tools/LightResults.DevelopBenchmarks/Benchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down

0 comments on commit 1f01b63

Please sign in to comment.