All Projects → mcintyre321 → Oneof

mcintyre321 / Oneof

Licence: mit
Easy to use F#-like ~discriminated~ unions for C# with exhaustive compile time matching

Projects that are alternatives of or similar to Oneof

Quartznet
Quartz Enterprise Scheduler .NET
Stars: ✭ 4,825 (+511.53%)
Mutual labels:  dotnetcore
Winapi
A simple, direct, ultra-thin CLR library for high-performance Win32 Native Interop
Stars: ✭ 636 (-19.39%)
Mutual labels:  dotnetcore
Natasha
基于 Roslyn 的 C# 动态程序集构建库,该库允许开发者在运行时使用 C# 代码构建域 / 程序集 / 类 / 结构体 / 枚举 / 接口 / 方法等,使得程序在运行的时候可以增加新的模块及功能。Natasha 集成了域管理/插件管理,可以实现域隔离,域卸载,热拔插等功能。 该库遵循完整的编译流程,提供完整的错误提示, 可自动添加引用,完善的数据结构构建模板让开发者只专注于程序集脚本的编写,兼容 stanadard2.0 / netcoreapp3.0+, 跨平台,统一、简便的链式 API。 且我们会尽快修复您的问题及回复您的 issue.
Stars: ✭ 705 (-10.65%)
Mutual labels:  dotnetcore
Modular Monolith With Ddd
Full Modular Monolith application with Domain-Driven Design approach.
Stars: ✭ 6,210 (+687.07%)
Mutual labels:  dotnetcore
Raspberryio
The Raspberry Pi's IO Functionality in an easy-to-use API for Mono/.NET/C#
Stars: ✭ 593 (-24.84%)
Mutual labels:  dotnetcore
Try Convert
Helping .NET developers port their projects to .NET Core!
Stars: ✭ 671 (-14.96%)
Mutual labels:  dotnetcore
Finbuckle.multitenant
Finbuckle.MultiTenant is an open source multitenancy library for .NET. It provides tenant resolution, per-tenant app behavior, and per-tenant data isolation.
Stars: ✭ 478 (-39.42%)
Mutual labels:  dotnetcore
Smartsql
SmartSql = MyBatis in C# + .NET Core+ Cache(Memory | Redis) + R/W Splitting + PropertyChangedTrack +Dynamic Repository + InvokeSync + Diagnostics
Stars: ✭ 775 (-1.77%)
Mutual labels:  dotnetcore
Mockhttp
Testing layer for Microsoft's HttpClient library. Create canned responses using a fluent API.
Stars: ✭ 623 (-21.04%)
Mutual labels:  dotnetcore
Flubucore
A cross platform build and deployment automation system for building projects and executing deployment scripts using C# code.
Stars: ✭ 695 (-11.91%)
Mutual labels:  dotnetcore
Nsmartproxy
NSmartProxy是一款开源免费的内网穿透工具。采用.NET CORE的全异步模式打造。(NSmartProxy is an open source reverse proxy tool that creates a secure tunnel from a public endpoint to a locally service.)
Stars: ✭ 547 (-30.67%)
Mutual labels:  dotnetcore
Granular
WPF for JavaScript
Stars: ✭ 569 (-27.88%)
Mutual labels:  dotnetcore
Rawrabbit
A modern .NET framework for communication over RabbitMq
Stars: ✭ 682 (-13.56%)
Mutual labels:  dotnetcore
Consoleframework
Cross-platform toolkit for easy development of TUI applications.
Stars: ✭ 487 (-38.28%)
Mutual labels:  dotnetcore
Ocelot
.NET core API Gateway
Stars: ✭ 6,675 (+746.01%)
Mutual labels:  dotnetcore
Wpfdesigner
The WPF Designer from SharpDevelop
Stars: ✭ 479 (-39.29%)
Mutual labels:  dotnetcore
Framework
A lightweight low ceremony API for web services.
Stars: ✭ 644 (-18.38%)
Mutual labels:  dotnetcore
Passcore
A self-service password management tool for Active Directory
Stars: ✭ 787 (-0.25%)
Mutual labels:  dotnetcore
Weapsy
ASP.NET Core CMS
Stars: ✭ 748 (-5.2%)
Mutual labels:  dotnetcore
Zxw.framework.netcore
基于EF Core的Code First模式的DotNetCore快速开发框架,其中包括DBContext、IOC组件autofac和AspectCore.Injector、代码生成器(也支持DB First)、基于AspectCore的memcache和Redis缓存组件,以及基于ICanPay的支付库和一些日常用的方法和扩展,比如批量插入、更新、删除以及触发器支持,当然还有demo。欢迎提交各种建议、意见和pr~
Stars: ✭ 691 (-12.42%)
Mutual labels:  dotnetcore

OneOf

"Ah! It's like a compile time checked switch statement!" - Mike Giorgaras

Getting Started

install-package OneOf

This library provides F# style discriminated unions for C#, using a custom type OneOf<T0, ... Tn>. An instance of this type holds a single value, which is one of the types in its generic argument list.

I can't encourage you enough to give it a try! Due to exhaustive matching DUs provide an alternative to polymorphism when you want to have a method with guaranteed behaviour-per-type (i.e. adding an abstract method on a base type, and then implementing that method in each type). It's a really powerful tool, ask any f#/Scala dev! :)

PS If you like OneOf, you might want to check out ValueOf, for one-line Value Object Type definitions.

Use cases

As a method return value

The most frequent use case is as a return value, when you need to return different results from a method. Here's how you might use it in an MVC controller action:

public OneOf<User, InvalidName, NameTaken> CreateUser(string username)
{
    if (!IsValid(username)) return new InvalidName();
    var user = _repo.FindByUsername(username);
    if(user != null) return new NameTaken();
    var user = new User(username);
    _repo.Save(user);
    return user;
}

[HttpPost]
public IActionResult Register(string username)
{
    OneOf<User, InvalidName, NameTaken> createUserResult = CreateUser(username);
    return createUserResult.Match(
        user => new RedirectResult("/dashboard"),
        invalidName => {
            ModelState.AddModelError(nameof(username), $"Sorry, that is not a valid username.");
            return View("Register");
        },
        nameTaken => {
            ModelState.AddModelError(nameof(username), "Sorry, that name is already in use.");
            return View("Register");
        }
    );
}

As an 'Option' Type

It's simple to use OneOf as an Option type - just declare a OneOf<Something, None>. OneOf comes with a variety of useful Types in the OneOf.Types namespace, including Yes, No, Maybe, Unknown, True, False, All, Some, and None.

Benefits

  • True strongly typed method signature
    • No need to return a custom result base type e.g IActionResult, or even worse, a non-descriptive type (e.g. object)
    • The method signature accurately describes all the potential outcomes, making it easier for consumers to understand the code
    • Method consumer HAS to handle all cases (see 'Matching', below)
  • You can avoid using "Exceptions for control flow" antipattern by returning custom Typed error objects

As a method parameter value

You can use also use OneOf as a parameter type, allowing a caller to pass different types without requiring additional overloads. This might not seem that useful for a single parameter, but if you have multiple parameters, the number of overloads required increases rapidly.

public void SetBackground(OneOf<string, ColorName, Color> backgroundColor) { ... }

//The method above can be called with either a string, a ColorName enum value or a Color instance.

Matching

You use the TOut Match(Func<T0, TOut> f0, ... Func<Tn,TOut> fn) method to get a value out. Note how the number of handlers matches the number of generic arguments.

Advantages over switch or if or exception based control flow:

This has a major advantage over a switch statement, as it

  • requires every parameter to be handled

  • No fallback - if you add another generic parameter, you HAVE to update all the calling code to handle your changes.

    In brown-field code-bases this is incredibly useful, as the default handler is often a runtime throw NotImplementedException, or behaviour that wouldn't suit the new result type.

E.g.

OneOf<string, ColorName, Color> backgroundColor = ...;
Color c = backgroundColor.Match(
    str => CssHelper.GetColorFromString(str),
    name => new Color(name),
    col => col
);
_window.BackgroundColor = c;

There is also a .Switch method, for when you aren't returning a value:

OneOf<string, DateTime> dateValue = ...;
dateValue.Switch(
    str => AddEntry(DateTime.Parse(str), foo),
    int => AddEntry(int, foo)
);

TryPick𝑥 method

As an alternative to .Switch or .Match you can use the .TryPick𝑥 methods.

//TryPick𝑥 methods for OneOf<T0, T1, T2>
public bool TryPickT0(out T0 value, out OneOf<T1, T2> remainder) { ... }
public bool TryPickT1(out T1 value, out OneOf<T0, T2> remainder) { ... }
public bool TryPickT2(out T2 value, out OneOf<T0, T1> remainder) { ... }

The return value indicates if the OneOf contains a T𝑥 or not. If so, then value will be set to the inner value from the OneOf. If not, then the remainder will be a OneOf of the remaining generic types. You can use them like this:

IActionResult Get(string id)
{
    OneOf<Thing, NotFound, Error> thingOrNotFoundOrError = GetThingFromDb(string id);

    if (thingOrNotFoundOrError.TryPickT1(out NotFound notFound, out var thingOrError)) //thingOrError is a OneOf<Thing, Error>
      return StatusCode(404);

    if (thingOrError.TryPickT1(out var error, out var thing)) //note that thing is a Thing rather than a OneOf<Thing>
    {
      _logger.LogError(error.Message);
      return StatusCode(500);
    }

    return Ok(thing);
}

Reusable OneOf Types using OneOfBase

You can declare a OneOf as a type, either for reuse of the type, or to provide additional members, by inheriting from OneOfBase. The derived class will inherit the .Match, .Switch, and .TryPick𝑥 methods.

public class StringOrNumber : OneOfBase<string, int>
{
    StringOrNumber(OneOf<string, int> _) : base(_) { }

    // optionally, define implicit conversions
    // you could also make the constructor public
    public static implicit operator StringOrNumber(string _) => new StringOrNumber(_);
    public static implicit operator StringOrNumber(int _) => new StringOrNumber(_);

    public (bool isNumber, int number) TryGetNumber() =>
        Match(
            s => (int.TryParse(s, out var n), n),
            i => (true, i)
        );
}

StringOrNumber x = 5;
Console.WriteLine(x.TryGetNumber().number);
// prints 5

x = "5";
Console.WriteLine(x.TryGetNumber().number);
// prints 5

x = "abcd";
Console.WriteLine(x.TryGetNumber().isNumber);
// prints False
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].