• API Reference

    Show / Hide Table of Contents
    • Recore
      • AbsoluteUri
      • AsyncAction
      • AsyncAction<T>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7, T8, T9>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7, T8>
      • AsyncAction<T1, T2, T3, T4, T5, T6, T7>
      • AsyncAction<T1, T2, T3, T4, T5, T6>
      • AsyncAction<T1, T2, T3, T4, T5>
      • AsyncAction<T1, T2, T3, T4>
      • AsyncAction<T1, T2, T3>
      • AsyncAction<T1, T2>
      • AsyncDefer
      • AsyncFunc<T, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, T8, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, T7, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, T6, TResult>
      • AsyncFunc<T1, T2, T3, T4, T5, TResult>
      • AsyncFunc<T1, T2, T3, T4, TResult>
      • AsyncFunc<T1, T2, T3, TResult>
      • AsyncFunc<T1, T2, TResult>
      • AsyncFunc<TResult>
      • Defer
      • Either
      • Either<TLeft, TRight>
      • Func
      • ObjectExtensions
      • Of<T>
      • OfJsonAttribute
      • Optional
      • Optional<T>
      • RelativeUri
      • Result
      • Result.AsyncCatcher<TValue>
      • Result.Catcher<TValue>
      • Result<TValue, TError>
      • Unit
      • UriExtensions
    • Recore.Collections.Generic
      • AnonymousEqualityComparer<T>
      • ICollectionExtensions
      • IDictionaryExtensions
      • IIterator<T>
      • Iterator
      • LinkedListExtensions
      • ListExtensions
      • MappedComparer<T, TMapped>
      • MappedEqualityComparer<T, TMapped>
    • Recore.Linq
      • Renumerable
    • Recore.Security.Cryptography
      • SecureCompare
    • Recore.Text.Json.Serialization.Converters
      • OverrideEitherConverter<TLeft, TRight>
      • OverrideResultConverter<TValue, TError>
    • Recore.Threading.Tasks
      • TaskExtensions

    Class Result<TValue, TError>

    Represents the result of an operation that can be successful or failed.

    Inheritance
    Object
    Result<TValue, TError>
    Implements
    IEquatable<Result<TValue, TError>>
    Inherited Members
    Object.Equals(Object, Object)
    Object.GetType()
    Object.MemberwiseClone()
    Object.ReferenceEquals(Object, Object)
    Namespace: Recore
    Assembly: Recore.dll
    Syntax
    [JsonConverter(typeof(ResultConverter))]
    public sealed class Result<TValue, TError> : IEquatable<Result<TValue, TError>>
    Type Parameters
    Name Description
    TValue
    TError
    Remarks

    When working with System.Text.Json, deserializing into Result<TValue, TError> can be ambiguous. The default deserialization behavior will try first to deserialize as TValue, and then as TError. But, this may return the unintended type if the value deserializer can successfully deserialize the JSON representation of the error.

    A common case is when both TValue and TError are POCOs. In that case, Result<TValue, TError> will always deserialize as TValue, filling in default values for any missing properties. For example:

    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    
    class Address
    {
        public string Street { get; set; }
        public string Zip { get; set; }
    }
    
    // Deserializes as a `Person`!
    JsonSerializer.Deserialize<Result<Person, Address>>("{\"Street\":\"123 Main St\",\"Zip\":\"12345\"}")

    You can use OverrideResultConverter<TValue, TError> to specify how to choose between TValue and TError based on the properties in the JSON:

    // Look at the JSON to decide which type we have
    options.Converters.Add(new OverrideResultConverter<Person, Address>(
        deserializeAsLeft: json => json.TryGetProperty("Street", out JsonElement _)));
    
    // Deserializes correctly
    JsonSerializer.Deserialize<Result<Person, Address>>("{\"Street\":\"123 Main St\",\"Zip\":\"12345\"}", options)
    Examples

    Result<TValue, TError> is basically a reskin of Either<TLeft, TRight> with some additional semantics.

    To explain the use case, let's think about exception-based and status-code-based error handling. (If you haven't read it before, I strongly recommend Joe Duffy's blog post on the topic.)

    For an example of exception-based handling, think of Parse(String). For an example of exception-based handling, think of Int32.TryParse(String, out Int32).

    Basically, the debate looks like this:

    Pros Cons
    Status codes
    • Low perf hit
    • Better choice for when errors are expected, like when validating untrusted data
    • Mess up the method signature
    • Can be ignored or forgotten about
    Exceptions
    • Let code focus on happy-path logic and push error handling up into another layer
    • Can't be ignored, preventing unsafe execution of the program
    • Horribly slow when thrown (dominated by collecting the stack trace)
    • Can be forgotten about, leading to your users seeing internal error messages or introducing bugs in the error flow

    As you can tell, exceptions are great for handling fatal errors. But what about recoverable errors? Status codes are a better choice, but are a bit primitive.

    This is where Result<TValue, TError> comes in. It has the same pros as status codes, but addresses the cons:

    • Less disruptive to the method signature; just return Result<TValue, TError>
    • Can't get to the value unless you handle the error case

    For an example, imagine you're calling HttpClient.GetAsync() to fetch some JSON data and deserialize it. Its signature is

    Task<HttpResponseMessage> GetAsync(string requestUri);
    

    With exceptions, you could implement it like this:

    async Task<Person> GetPersonAsync(int id)
    {
        var response = await httpClient.GetAsync($"/api/v1/person/{id}");
        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException(response.StatusCode.ToString());
        }
    
        var json = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<Person>(json);
    }
    

    But HTTP calls fail for all kinds of reasons. Who handles that exception? How do you decide to retry? How do you report the error to the user?

    A better choice is to go with Result<TValue, TError>:

    async Task<Result<Person, HttpStatusCode>> GetPersonAsync(int id)
    {
        var response = await httpClient.GetAsync($"/api/v1/person/{id}");
        if (response.IsSuccessStatusCode)
        {
            var json = await response.Content.ReadAsStringAsync();
            var person = JsonSerializer.Deserialize<Person>(json);
            return Result.Success<Person, HttpStatusCode>(person);
        }
        else
        {
            return Result.Failure<Person, HttpStatusCode>(response.StatusCode);
        }
    }
    

    If you're working with an API that returns a special error response in case of errors (such as GitHub), you can even do this:

    async Task<Result<Person, Error>> GetPersonAsync(int id)
    {
        // ...
        else
        {
            var json = await response.Content.ReadAsStringAsync();
            var error = JsonSerializer.Deserialize<Error>(json);
            return Result.Failure<Person, HttpStatusCode>(error);
        }
    }
    

    Anyway, let's say we just pass on the status code. Downstream code can then handle the error like this:

    async Task<bool> GetPersonAndPrint(int id)
    {
        bool retry = false;
        var personResult = await GetPersonAsync(id);
        personResult.Switch(
            person => Console.WriteLine(person),
            status =>
            {
                if ((int)status >= 500)
                {
                    retry = true;
                }
                else
                {
                    Console.Error.WriteLine($"Fatal error: {status}");
                }
            });
    
        return retry;
    }
    

    It also makes it easy to build up an error context as you go along rather than terminating immediately (see this code):

    Result<IBlob, IBlob>[] results = await Task.WhenAll(blobsToWrite.Select(blob =>
        Result.TryAsync(async () =>
        {
            await WriteBlobAsync(blob);
            return blob;
        })
        .CatchAsync((Exception e) =>
        {
            Console.Error.WriteLine(e);
            return Task.FromResult(blob);
        })));
    
    List<IBlob> successes = results.Successes().ToList();
    List<IBlob> failures = results.Failures().ToList();
    

    Constructors

    | Improve this Doc View Source

    Result(TValue)

    Constructs an instance of the type from a value of TValue.

    Declaration
    public Result(TValue value)
    Parameters
    Type Name Description
    TValue value
    | Improve this Doc View Source

    Result(TError)

    Constructs an instance of the type from a value of TError.

    Declaration
    public Result(TError error)
    Parameters
    Type Name Description
    TError error

    Properties

    | Improve this Doc View Source

    IsSuccessful

    Indicates whether the result is successful.

    Declaration
    public bool IsSuccessful { get; }
    Property Value
    Type Description
    Boolean

    Methods

    | Improve this Doc View Source

    Equals(Result<TValue, TError>)

    Compares two instances of Result<TValue, TError> for equality.

    Declaration
    public bool Equals(Result<TValue, TError> other)
    Parameters
    Type Name Description
    Result<TValue, TError> other
    Returns
    Type Description
    Boolean
    Remarks

    Equality is defined as both objects' underlying values or errors being equal.

    | Improve this Doc View Source

    Equals(Result<TValue, TError>, IEqualityComparer<TValue>, IEqualityComparer<TError>)

    Compares two instances of Result<TValue, TError> for equality using the given IEqualityComparer<T>.

    Declaration
    public bool Equals(Result<TValue, TError> other, IEqualityComparer<TValue> valueComparer, IEqualityComparer<TError> errorComparer)
    Parameters
    Type Name Description
    Result<TValue, TError> other
    IEqualityComparer<TValue> valueComparer
    IEqualityComparer<TError> errorComparer
    Returns
    Type Description
    Boolean
    | Improve this Doc View Source

    Equals(Object)

    Compares this Result<TValue, TError> to another object for equality.

    Declaration
    public override bool Equals(object obj)
    Parameters
    Type Name Description
    Object obj
    Returns
    Type Description
    Boolean
    Overrides
    Object.Equals(Object)
    Remarks

    Two Result<TValue, TError>s are equal only if they have the same type parameters in the same order. For example, an Result<int, string> and an Result<string, int> will always be nonequal.

    | Improve this Doc View Source

    GetError()

    Converts Result<TValue, TError> to Optional<TError>

    Declaration
    public Optional<TError> GetError()
    Returns
    Type Description
    Optional<TError>
    | Improve this Doc View Source

    GetHashCode()

    Returns the hash code of the underlying value.

    Declaration
    public override int GetHashCode()
    Returns
    Type Description
    Int32
    Overrides
    Object.GetHashCode()
    | Improve this Doc View Source

    GetValue()

    Converts Result<TValue, TError> to Optional<TValue>

    Declaration
    public Optional<TValue> GetValue()
    Returns
    Type Description
    Optional<TValue>
    | Improve this Doc View Source

    IfError(Action<TError>)

    Takes an action only if the the result is failed.

    Declaration
    public void IfError(Action<TError> onError)
    Parameters
    Type Name Description
    Action<TError> onError
    | Improve this Doc View Source

    IfValue(Action<TValue>)

    Takes an action only if the result is successful.

    Declaration
    public void IfValue(Action<TValue> onValue)
    Parameters
    Type Name Description
    Action<TValue> onValue
    | Improve this Doc View Source

    OnError<TResult>(Func<TError, TResult>)

    Maps a function over the Result<TValue, TError> only if the result is failed.

    Declaration
    public Result<TValue, TResult> OnError<TResult>(Func<TError, TResult> onError)
    Parameters
    Type Name Description
    Func<TError, TResult> onError
    Returns
    Type Description
    Result<TValue, TResult>
    Type Parameters
    Name Description
    TResult
    | Improve this Doc View Source

    OnValue<TResult>(Func<TValue, TResult>)

    Maps a function over the Result<TValue, TError> only if the result is successful.

    Declaration
    public Result<TResult, TError> OnValue<TResult>(Func<TValue, TResult> onValue)
    Parameters
    Type Name Description
    Func<TValue, TResult> onValue
    Returns
    Type Description
    Result<TResult, TError>
    Type Parameters
    Name Description
    TResult
    | Improve this Doc View Source

    Switch(Action<TValue>, Action<TError>)

    Takes one of two actions depending on whether the result is successful.

    Declaration
    public void Switch(Action<TValue> onValue, Action<TError> onError)
    Parameters
    Type Name Description
    Action<TValue> onValue
    Action<TError> onError
    | Improve this Doc View Source

    Switch<T>(Func<TValue, T>, Func<TError, T>)

    Calls one of two functions depending on whether the result is successful.

    Declaration
    public T Switch<T>(Func<TValue, T> onValue, Func<TError, T> onError)
    Parameters
    Type Name Description
    Func<TValue, T> onValue
    Func<TError, T> onError
    Returns
    Type Description
    T
    Type Parameters
    Name Description
    T
    | Improve this Doc View Source

    Then<TResult>(Func<TValue, Result<TResult, TError>>)

    Chains another Result<TValue, TError>-producing operation from another.

    Declaration
    public Result<TResult, TError> Then<TResult>(Func<TValue, Result<TResult, TError>> f)
    Parameters
    Type Name Description
    Func<TValue, Result<TResult, TError>> f
    Returns
    Type Description
    Result<TResult, TError>
    Type Parameters
    Name Description
    TResult
    Remarks

    This is a monad bind operation. Conceptually, it is the same as passing f to OnValue<TResult>(Func<TValue, TResult>) and then "flattening" the result.

    | Improve this Doc View Source

    ToString()

    Returns the string representation of the underlying value or error.

    Declaration
    public override string ToString()
    Returns
    Type Description
    String
    Overrides
    Object.ToString()

    Operators

    | Improve this Doc View Source

    Equality(Result<TValue, TError>, Result<TValue, TError>)

    Determines whether two instances of Result<TValue, TError> have the same value.

    Declaration
    public static bool operator ==(Result<TValue, TError> lhs, Result<TValue, TError> rhs)
    Parameters
    Type Name Description
    Result<TValue, TError> lhs
    Result<TValue, TError> rhs
    Returns
    Type Description
    Boolean
    | Improve this Doc View Source

    Implicit(TValue to Result<TValue, TError>)

    Converts an instance of a type to an Result<TValue, TError>.

    Declaration
    public static implicit operator Result<TValue, TError>(TValue value)
    Parameters
    Type Name Description
    TValue value
    Returns
    Type Description
    Result<TValue, TError>
    | Improve this Doc View Source

    Implicit(TError to Result<TValue, TError>)

    Converts an instance of a type to an Result<TValue, TError>.

    Declaration
    public static implicit operator Result<TValue, TError>(TError error)
    Parameters
    Type Name Description
    TError error
    Returns
    Type Description
    Result<TValue, TError>
    | Improve this Doc View Source

    Inequality(Result<TValue, TError>, Result<TValue, TError>)

    Determines whether two instances of Result<TValue, TError> have different values.

    Declaration
    public static bool operator !=(Result<TValue, TError> lhs, Result<TValue, TError> rhs)
    Parameters
    Type Name Description
    Result<TValue, TError> lhs
    Result<TValue, TError> rhs
    Returns
    Type Description
    Boolean

    Implements

    System.IEquatable<T>

    Extension Methods

    ObjectExtensions.StaticCast<T>(T)
    ObjectExtensions.Apply<T, TResult>(T, Func<T, TResult>)
    ObjectExtensions.Apply<T>(T, Action<T>)
    • Improve this Doc
    • View Source
    Back to top Generated by DocFX