{"id":30780381,"url":"https://github.com/drizin/oneof.deconstructorextensions","last_synced_at":"2025-09-05T07:12:57.769Z","repository":{"id":253593350,"uuid":"843964983","full_name":"Drizin/OneOf.DeconstructorExtensions","owner":"Drizin","description":"Extends OneOf\u003c\u003e and OneOfBase\u003c\u003e with methods to deconstruct their underlying types, while converting non-nullable value-types into nullable types (so only one of the resulting values will be non-null). Also provides extensions to convert into Tuple\u003c\u003e or ValueTuple\u003c\u003e","archived":false,"fork":false,"pushed_at":"2024-08-19T02:17:26.000Z","size":181,"stargazers_count":11,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-29T05:25:56.568Z","etag":null,"topics":["oneof","result-pattern"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Drizin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-18T00:43:41.000Z","updated_at":"2025-04-13T16:39:20.000Z","dependencies_parsed_at":"2024-08-18T01:43:50.044Z","dependency_job_id":null,"html_url":"https://github.com/Drizin/OneOf.DeconstructorExtensions","commit_stats":null,"previous_names":["drizin/oneof.totupleextensions"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Drizin/OneOf.DeconstructorExtensions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drizin%2FOneOf.DeconstructorExtensions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drizin%2FOneOf.DeconstructorExtensions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drizin%2FOneOf.DeconstructorExtensions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drizin%2FOneOf.DeconstructorExtensions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Drizin","download_url":"https://codeload.github.com/Drizin/OneOf.DeconstructorExtensions/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Drizin%2FOneOf.DeconstructorExtensions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273723993,"owners_count":25156450,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["oneof","result-pattern"],"created_at":"2025-09-05T07:12:55.213Z","updated_at":"2025-09-05T07:12:57.746Z","avatar_url":"https://github.com/Drizin.png","language":"C#","readme":"[![Nuget](https://img.shields.io/nuget/v/OneOf.ToTupleExtensions?label=OneOf.ToTupleExtensions)](https://www.nuget.org/packages/OneOf.ToTupleExtensions)\n[![Downloads](https://img.shields.io/nuget/dt/OneOf.ToTupleExtensions.svg)](https://www.nuget.org/packages/OneOf.ToTupleExtensions)\n\n# OneOf\n\n[OneOf](https://github.com/mcintyre321/OneOf/) is a popular library that enhances C# with discriminated unions, which basically means that you can create types that can represent different types - e.g. an instance of  `OneOf\u003cint, string, float\u003e` will hold a single value that will either be an `int`, or a `string`, or a `float`.\n\nDiscriminated unions are a great way to do exhaustive type matching, which means that the **compiler can enforce that all possible types are being handled**:\n- By using `OneOf\u003c\u003e.Switch()` you **need** to specify an an `Action\u003cT\u003e` for each of possible type `T`\n- By using `OneOf\u003c\u003e.Match()` you **need** to specify a `Func\u003cT, TResult\u003e` that will convert each possible type `T` into a single type `TResult`\n\n`OneOf` library intentionally does NOT allow the underlying types to be deconstructed because it kind of [defeats the purpose](https://github.com/mcintyre321/OneOf/pull/114) of the library, which is exactly to enforce compile-time exhaustive type matching.\n\nHowever, in some cases it might be helpful to **deconstruct** the OneOf object into its underlying types...\n\n# OneOf.DeconstructorExtensions\n\n**This library extends `OneOf\u003c\u003e` and `OneOfBase\u003c\u003e` with deconstructor methods to extract the underlying types**, while using the same semantic as discriminated-union: **only ONE of the return types will be non-null**.\n\nThe major problem solved by this library is that deconstruction is not trivial because of non-nullable value types:\n- When we have an instance of `OneOf\u003cT0, T1, ...\u003e` and we deconstruct it into its underlying types (`T0`, `T1`, ...) we want to have the **same semantic as the union-type**:  **we want only one element to be non-null**\n- This means that any type that is a non-nullable value type (like a primitive type, or `struct` or an `enum`) would have to be converted into an equivalent nullable type. (e.g. `int` should become `Nullable\u003cint\u003e` so it can be `null`).\n- The problem is that we can't just have a single deconstructor that converts all types `T` to `Nullable\u003cT\u003e`, as this can only be done for non-nullable value types.\n- This means that we need a lot of overloads. Let's say we're talking about `OneOf\u003cT0,T1\u003e` - each one of those types `T` may or may not be a non-nullable value type, so we will have 4 different combinations, all having same signature but different `where` constraints for the generic-types. For `OneOf\u003cT0,T1,T2\u003e` we have 8 combinations, etc - this goes up to `OneOf\u003cT0,T1,T2,T3,T4,T5,T6\u003e` which has 128 combinations.\n- Each one of those combinations will convert only the right types to `Nullable\u003c\u003e`\n\nAll the hundreds of overloads are created by a [CodegenCS](https://github.com/Drizin/CodegenCS) code generator [template](/src/CodeGenerator/GeneratorTemplate.cs) (output example [here](/src/OneOf.ToTupleExtensions/OneOfBaseConvertToTupleExtensions.generated.cs)).\n\nAs a more concrete example, let's say you have a class `SalesOrder` and a method with this signature:\n```cs\npublic OneOf\u003cSalesOrder, CreateSalesOrderErrorEnum\u003e CreateSalesOrder(etc..)\n```\nIf you do a deconstruction like `var (order, error) = CreateSalesOrder(...)` and the method succeeds (returns a sales order), you would expect that the deconstructed `order` is not-null and `error` is null.  \nHowever, since `CreateSalesOrderErrorEnum` is enum (non-nullable value type), this deconstruction (where the first type `T0` is a reference-type and the second type `T1` is a non-nullable value type) should return `T0?` and `Nullable\u003cT1\u003e` (in other words `SalesOrder?` and `Nullable\u003cCreateSalesOrderErrorEnum\u003e`). So basically our deconstruction overloads will always find the right combination of types to ensure that all return types are nullable, and only one of the results is a non-null value.\n\n\n# OneOf.ToTupleExtensions\n\n**This library extends `OneOf\u003c\u003e` and `OneOfBase\u003c\u003e` with methods to convert them into `Tuple\u003c\u003e` or `ValueTuple\u003c\u003e`** (which have methods to deconstruct into the underlying types), and uses the same logic as `OneOf.DeconstructorExtensions`: non-nullable value types are converted to nullable types so that only one element of the tuple will be non-null.\n\nOne specific challenge here (doesn't happen for deconstructors but happens here) is that the compiler does NOT consider constraints as part of the method signature, so the compiler thinks that all overloads are possible matches - and we have to use an [overload-resolution hack](https://stackoverflow.com/questions/2974519/generic-constraints-where-t-struct-and-where-t-class/36775837#36775837) to let the compiler correctly disambiguate between the multiple overloads (which all have identical signature) and use the right method (where we convert only the right types to `Nullable\u003c\u003e`).\n\n\n## Subclassing alternative\n\nIf you are subclassing from `OneOfBase\u003c\u003e` (instead of using the standard `OneOf\u003c\u003e` structs) then instead of using our extension methods you could create your own deconstructor. \nYou can subclass with concrete types:\n```cs\npublic class CreateUserResult : OneOfBase\u003cUser, CreateUserErrorEnum, List\u003cValidationError\u003e {...}\n```\n\n...or you can subclass with generic types with explicit constraints\n```cs\npublic class Result\u003cTResult, TErrorEnum\u003e : OneOfBase\u003cTResult, TErrorEnum, List\u003cValidationError\u003e\u003e\n    where TResult: class\n    where TErrorEnum: struct\n{...}\n```\n\nIn both cases it's clear to the compiler what types are non-nullable value types and you can just handle that (and convert to nullable types) in your deconstructor.\n\nBut why would you manually write your own classes and destructors when you can just use `OneOf\u003c\u003e` and use our extension-methods? :-) \n\n\u003chr\u003e\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\n# Motivation: using Discriminated Unions for the Result Pattern\n\nThe Result Pattern is a way of representing the outcome of an operation (whether it's successful or has encountered an error) in a more explicit and structured manner. It's an alternative to exception-based error handling. (Hold your horses - I'm not telling that you should never use exceptions - I'm just telling that there are some other design patterns that [might be used in conjunction](https://stackoverflow.com/a/46823898/3606250) to exceptions and in some cases might be better than throwing exceptions, especially if you're using [exceptions as flow control](https://www.youtube.com/watch?v=E3dU9Y1CsnI)). This pattern can be used by writing your own wrappers or by using some well-established libraries like [FluentResults](https://github.com/altmann/FluentResults), [error-or](https://github.com/amantinband/error-or), [ardalis Result](https://github.com/ardalis/Result), and [many others](https://github.com/youssefsell/Result.net). \n\nOne downside of using a third-party library for Result pattern is that you will have limited degree of freedom for extensibility. As an example if you use [FluentResults](https://github.com/altmann/FluentResults) your methods will return a `Result` if they don't return any object in case of a success and will return `Result\u003cT\u003e` if they return type `T` in case of success - but in both types the errors are stored in a very generic `List\u003cIError\u003e Errors` which is very generic: it can both contain one or more errors, they can be of any type that implements `IError`, they can have different types inside it, etc. So basically the signature of a method `Result\u003cSalesOrder\u003e` doesn't give us any hint on what kind of errors we may get inside this result object.\n\nBy using **discriminated unions** and more specifically the [OneOf](https://github.com/mcintyre321/OneOf/) library we can solve this problem by making more explicit what are the possible errors that each method can return. As an example, consider the following signature:\n\n```cs\npublic OneOf\u003cSuccess\u003cUser\u003e, Error\u003cOutOfStockError\u003e, Error\u003cstring\u003e\u003e CreateSalesOrder(SalesOrder order)\n```\n\nIt's clear from the signature that the method will either return a `SalesOrder` (in case of success) or a `OutOfStockError` instance (which probably requires some special handling from the immediate-caller) or else some generic error represented by a single `string`, which probably should just be displayed to the user.\n\nThe `Success\u003cT\u003e` wrapper and `Error\u003cT\u003e` wrapper are just dummy wrappers that provide a clearer semantic on each type, but they are not required. You could for example have something like this:\n\n```cs\npublic OneOf\u003cSalesOrder, CreateSalesOrderErrorEnum, List\u003cValidationError\u003e\u003e CreateSalesOrder(SalesOrder order)\n```\n\nIt's also clear from the signature that this method will either return a `SalesOrder` or a single error from a given enum type (which probably the immediate-caller should act upon) or a list of one-or-more validation errors that should probably just displayed in the UI.\n\nTo sum, discriminated unions provide a more idiomatic way of using the Result Pattern. Even when we don't want/need exhaustive type matching...\n\n\n# Result-Pattern example (using OneOf and OneOf Tuple Deconstruction)\n\nLet's say your service returns a discriminated-union like this:\n\n```cs\n/// \u003csummary\u003e\n/// CreateUser returns ONLY one of these types (the others will be null): \n/// - a successful result (a User object)\n/// - an error (of enum type) \n/// - one-or-more validation errors\n/// \u003c/summary\u003e\npublic OneOf\u003cUser, CreateUserErrorEnum, List\u003cValidationError\u003e\u003e CreateUser(CreateUserDTO newUserInfo)\n{\n    // Validation errors should be the first ones to be tested and early return\n    // I model them as \"ValidationErrors\" because the caller just pass those errors back to the UI, doesn't act upon them\n    if (string.IsNullOrEmpty(newUserInfo?.UserName))\n        return new List\u003cValidationError\u003e() { new ValidationError($\"{nameof(newUserInfo.UserName)} is required\") };\n\n    // Non-Validation errors are errors that might require the caller to act based on the errors\n    // (in this bad example there's not much to do other than pass the message to the user, but you get the idea - caller could SWITCH upon the enum and act)\n    if (newUserInfo!.UserName == \"Rick\")\n        return CreateUserErrorEnum.USERNAME_NOT_AVAILABLE;\n    if (string.IsNullOrEmpty(newUserInfo.Password) || newUserInfo.Password.Length \u003c 8)\n        return CreateUserErrorEnum.WEAK_PASSWORD;\n\n    // Early returns reduce the need for nested ifs. If everything went fine we return the resulting object\n    var user = new User() { FirstName = newUserInfo.FirstName, LastName = newUserInfo.LastName, UserName = newUserInfo.UserName };\n    return user;\n}\n```\n\nThe standard way of handling this with OneOf would be doing a `Switch()` with a lambda for each type:\n\n```cs\nvar oneOf = svc.CreateUser(createUserCommand);\noneOf.Switch(\n    user =\u003e {\n        // Successful return. Show created user or something like that\n    },\n    errorEnum =\u003e {\n        // handle whatever error we got (instead of an enum we could also get individual types for each possible error)\n    },\n    validationErrors =\u003e {\n        // Show the validation errors back to the UI\n    });\n```\n\nBut sometimes using switch and lambas can make our code complex and harder to maintain. Sometimes all we want is some early-returns.  \nWith ToTuple extensions and deconstruct operators we can use a concise golang-style syntax like this:\n\n\n```cs\n// Extensions enables us to use deconstruction (which does not exist in OneOf library)\nvar (user, error, validationErrors) = service.CreateUser(createUserCommand);\n\nif (validationErrors != null)\n{\n    // Show the validation errors back to the UI\n    return;\n}\n\n// If it wasn't for the Nullable conversion then errorEnum\n// would never possibly be null\nif (errorEnum != null)\n{\n    // switch and handle\n    return;\n}\n\n// else, success! (user!=null)\n// Usually this is the larger code-block, \n// and that's why having early-returns for the other cases makes this code cleaner and easy to maintain!\n// (no need to have a giant method with 3 different lambda-actions inside it)\n```\n\n\u003c!--\nThe style above is based in golang syntax and guidelines:\n\n- Regular expected error situations are handled with error results\n- **fatal unexpected situations** are handled with **\"panic\"**  (pretty much like Exceptions in C#/Java)\n- This encourages you to explicitly check for errors where they occur, as opposed to the paradigm that expects you to throw (and catch) exceptions even for expected errors.\n- Since the language has this clear distinction between exceptions and return errors, it has some [conventions and constructs for error handling](https://blog.golang.org/error-handling-and-go) that allows developers to easily (and concisely) get and test errors.\n- Basically, all functions where errors are expected to happen should always return an error object, and if the function is also expected to return some other object (in case of success) then it should use [**multiple return values**](https://gobyexample.com/multiple-return-values) so that it can simultaneously return both the expected result objects (at the first position) and then return the possible errors (at the last position)\n- **Multiple return values** makes the code more concise. By receiving the error as a regular return value our error handling becomes **less verbose**, **more explicit**, and **easier to read** since it uses regular if/else statements.\n- When returning multiple parameters **the return should always be ONE OR ANOTHER**, meaning that if there's an error you can expect that the other object is null, and vice-versa. This simple convention makes error handling much easier.\n\nLast, another major benefit of explicitly returning error codes is that **exceptions can easily be ignored while it's much harder to ignore errors if your methods force you to receive the returned error**. Returning codes obviously don't allow us to bubble-up automatically (as exceptions do), but the idea is exactly that - you want to check (and act) on your errors right where they occur.\n--\u003e\n\n\n\n## License\nMIT License\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrizin%2Foneof.deconstructorextensions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrizin%2Foneof.deconstructorextensions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrizin%2Foneof.deconstructorextensions/lists"}