iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
📝

Implementing a Result Type in C#

に公開

Preface

While using C#, I wondered if I could implement a so-called Result type, so I gave it a try.

Code

It is available on GitHub.
https://github.com/shunsock/dotnet_playground/tree/main/Utils

Usage

  • Import the following:
    • Utils.ResultType;
    • Utils.ResultType.Data;
    • Utils.ResultType.Fail;
  • Define a static function as follows:
    • Simple!
static ResultData<decimal> Divide(int a, int b)
{
    if (b == 0)
    {
        return Result.Fail<decimal>(ErrorCode.BadRequest);
    }

    return Result.Ok<decimal>((decimal) a / b);
}

Implementation

Directory Architecture

.
├── Program.cs
├── ResultType
│   ├── Data
│   │   └── ResultData.cs
│   ├── Fail
│   │   ├── ErrorCode.cs
│   │   └── ErrorMessage.cs
│   ├── Result.cs
│   └── Status
│       └── ResultStatus.cs
└── Utils.csproj

Result.cs (Static Class)

  • Defines the static functions Ok and Fail used in the function mentioned at the beginning.
  • Since the type of value is determined when Data is defined, Fail also uses <T>.
using Utils.ResultType.Data;
using Utils.ResultType.Fail;
using Utils.ResultType.Status;

namespace Utils.ResultType
{
    public static class Result
    {
        public static ResultData<T> Ok<T>(T value)
        {
            return new ResultData<T>(
                status: ResultStatus.Ok,
                value: value
            );
        }

        public static ResultData<T> Fail<T>(ErrorCode errorCode)
        {
            return new ResultData<T>(
                status: ResultStatus.Fail,
                errorCode: errorCode,
                value: default(T)
            );
        }
    }
}

Status

  • A simple Enum
namespace Utils.ResultType.Status
{
    public enum ResultStatus
    {
        Ok,
        Fail
    }
}

Fail

  • Simple error codes
namespace Utils.ResultType.Fail
{
    public enum ErrorCode
    {
        None,
        BadRequest,
        Unauthorized,
        Forbidden,
        NotFound
    }
}
  • A static function for displaying ErrorMessage
  • Takes an Enum as an argument and returns a string
namespace Utils.ResultType.Fail
{
    public static class ErrorMessage
    {
        public static string Show(ErrorCode code)
        {
            return code switch
            {
                ErrorCode.BadRequest => "Failed!: Bad Request",
                ErrorCode.Unauthorized => "Failed!: Unauthorized",
                ErrorCode.Forbidden => "Failed!: Forbidden",
                ErrorCode.NotFound => "Failed!: Not Found",
                ErrorCode.None => throw new InvalidOperationException(message: "ErrorCode is None"),
                _ => throw new ArgumentException(message: "Unknown ErrorCode")
            };
        }
    }
}

Data

  • The GetValue function was created with the following in mind:
    • If it is Fail, do not include data.
    • If it is Ok with the default value (i.e., Ok();), the caller will not use this function.
using Utils.ResultType.Fail;
using Utils.ResultType.Status;

namespace Utils.ResultType.Data
{
    public class ResultData<T>
    {
        public readonly ResultStatus Status;
        public readonly ErrorCode ErrorCode;
        private readonly T? _value;

        public ResultData(ResultStatus status, ErrorCode? errorCode = null, T? value = default)
        {
            Status = status;
            ErrorCode = errorCode == null ? ErrorCode.None : errorCode.Value; 
            _value = value;
        }

        public T GetValue()
        {
            if (Status == ResultStatus.Fail)
            {
                throw new InvalidOperationException("No Data with Status == Fail");
            }

            if (_value == null)
            {
                throw new InvalidOperationException(message: "Data is Null");
            }

            return _value!;
        }
    }
}

Discussion