base on Easy to use F#-like ~discriminated~ unions for C# with exhaustive compile time matching # OneOf [![NuGet](https://img.shields.io/nuget/v/OneOf?logo=nuget)](https://www.nuget.org/packages/OneOf/) [![GitHub](https://img.shields.io/github/license/mcintyre321/OneOf)](licence.md) > "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](https://github.com/mcintyre321/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: ```csharp 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"](http://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why) 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. ```csharp 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. ```csharp 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: ```csharp 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. ```csharp //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: ```csharp 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. ```csharp 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 ``` ### OneOfBase Source Generation You can automatically generate `OneOfBase` hierarchies using `GenerateOneOfAttribute` and partial class that extends `OneOfBase` using a Source Generator (thanks to @romfir for the contribution :D). Install it via > Install-Package OneOf.SourceGenerator and then define a stub like so: ```csharp [GenerateOneOf] public partial class StringOrNumber : OneOfBase<string, int> { } ``` During compilation the source generator will produce a class implementing the OneOfBase boiler plate code for you. e.g. ```csharp public partial class StringOrNumber { public StringOrNumber(OneOf.OneOf<System.String, System.Int32> _) : base(_) { } public static implicit operator StringOrNumber(System.String _) => new StringOrNumber(_); public static explicit operator System.String(StringOrNumber _) => _.AsT0; public static implicit operator StringOrNumber(System.Int32 _) => new StringOrNumber(_); public static explicit operator System.Int32(StringOrNumber _) => _.AsT1; } ``` ", Assign "at most 3 tags" to the expected json: {"id":"2557","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"