{"id":15036322,"url":"https://github.com/galad/csharpdiscriminatedunion","last_synced_at":"2025-04-09T23:23:47.137Z","repository":{"id":143462693,"uuid":"94624443","full_name":"Galad/CSharpDiscriminatedUnion","owner":"Galad","description":"A library for generating discriminated union types in C#","archived":false,"fork":false,"pushed_at":"2020-04-14T20:01:58.000Z","size":187,"stargazers_count":23,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-07T22:47:04.941Z","etag":null,"topics":["code-generation","code-generator","csharp-library","functional-programming"],"latest_commit_sha":null,"homepage":null,"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/Galad.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":"2017-06-17T13:09:22.000Z","updated_at":"2023-11-03T20:56:53.000Z","dependencies_parsed_at":"2023-03-27T13:06:09.493Z","dependency_job_id":null,"html_url":"https://github.com/Galad/CSharpDiscriminatedUnion","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Galad%2FCSharpDiscriminatedUnion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Galad%2FCSharpDiscriminatedUnion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Galad%2FCSharpDiscriminatedUnion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Galad%2FCSharpDiscriminatedUnion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Galad","download_url":"https://codeload.github.com/Galad/CSharpDiscriminatedUnion/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248126987,"owners_count":21052126,"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","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":["code-generation","code-generator","csharp-library","functional-programming"],"created_at":"2024-09-24T20:30:47.399Z","updated_at":"2025-04-09T23:23:47.095Z","avatar_url":"https://github.com/Galad.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# CSharpDiscriminatedUnion\n\n[![Build status](https://ci.appveyor.com/api/projects/status/kkfso0qs6hfer05h?svg=true)](https://ci.appveyor.com/project/Galad/csharpdiscriminatedunion)\n\nCSharpDiscriminatedUnion is code generation library that allow C# developers to create and use [discriminated unions](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions) in C#.\n\nIt relies on [CodeGeneration.Roslyn](https://github.com/AArnott/CodeGeneration.Roslyn) to generate C# code.\n\n## Table of Contents\n\n* [Overview][]\n* [Installation][]\n* [Template reference (class)][]\n* [Template reference (struct)][]\n* [Case values][]\n* [Using a struct][]\n* [Equality][]\n* [Match method][]\n\n## Overview\n[Overview]: #overview\nCSharpDiscriminatedUnion allows you to describe the base implementation of a discriminated union, and generates all the factory methods and the case matching method for you, as well as the equality implementations.\n\nHere is an example of a [Maybe](https://wiki.haskell.org/Maybe) type created with CSharpDiscriminatedUnion\n\n```csharp\n    [GenerateDiscriminatedUnion]\n    public partial class Maybe\u003cT\u003e\n    {\n        static partial class Cases\n        {\n            partial class None : Maybe\u003cT\u003e { }\n            partial class Just : Maybe\u003cT\u003e\n            {\n                readonly T value;\n            }\n        }\n    }\n```\n\nThe attribute enables the code generation for the type it annotates. \nThe type must be partial.\nIt can be a class or a structure. There is a slight difference on how class and structure discriminated union types are declared (see below for more details);\n\nA case is identified by a nested class that inherits the union type (here, `Maybe\u003cT\u003e`). The class name is the case identifier, and is used for the factory methods.\nCases must always be created in the nested static class Cases (`static partial class Cases`), which is generated automatically.\n\nOnce the code is generated, you have access to factory methods to create new instances, and a `Match` method, similar to pattern matching, that allows to deconstruct the value.\n\nHere are some examples of what you can do with the type `Maybe\u003cT\u003e`\n\n```csharp\n    // create a case None value\n    var none = Maybe\u003cint\u003e.None;\n    var three = Maybe\u003cint\u003e.Just(3);\n    var otherThree = Maybe\u003cint\u003e.Just(3);\n    var four = Maybe\u003cint\u003e.Just(3);\n    \n    Console.WriteLine(none); //writes \"None\"\n    Console.WriteLine(someValue); //writes \"3\"\n    Console.WriteLine(three == otherThree); //writes \"true\"\n    Console.WriteLine(three == none); //writes \"false\"\n    Console.WriteLine(three == four); //writes \"false\"\n    \n    private string MaybeToString(Maybe\u003cint\u003e value)\n    {\n       return value.Match(() =\u003e \"None\", i =\u003e i.ToString());\n    }\n```\n\n## Installation\n[Installation]: #installation\n* Install the NuGet package CSharpDiscriminatedUnion\n```xml\n    \u003cPackageReference Include=\"CSharpDiscriminatedUnion\" Version=\"1.0.0\" PrivateAssets=\"all\" /\u003e\n```\n* Add the required dotnet CLI tool:\n```xml\n    \u003cDotNetCliToolReference Include=\"dotnet-codegen\" Version=\"0.4.88\" /\u003e\n```\nMake sure that the version of the tool matches the version of CodeGeneration that is installed.\n\nFor more information see https://github.com/AArnott/CodeGeneration.Roslyn#packaging-up-your-code-generator-for-others-use\n\n## Template reference (class)\n[Template reference (class)]: #template-reference-class\n\n```csharp\n    [GenerateDiscriminatedUnion(CaseFactoryPrefix = \"New\", PreventNullValues = false)]\n    partial class [\u003cname\u003e]\n    {\n        static partial class Cases\n        {\n            // 0 or more classes\n            /// \u003csummary\u003e\n            /// [\u003ccase description\u003e]\n            /// \u003c/summary\u003e            \n            partial class [\u003ccase1Name\u003e] : [\u003cname\u003e]\n            {\n                // 0 or more fields\n                /// \u003csummary\u003e\n                /// [\u003cvalue description\u003e]\n                /// \u003c/summary\u003e                \n                readonly [type] [\u003ccase1Value\u003e];                \n            }\n        }\n    }\n```\n\n## Template reference (struct)\n[Template reference (struct)]: #template-reference-struct\n\n```csharp\n    [GenerateDiscriminatedUnion(CaseFactoryPrefix = [\u003cfactory method prefix\u003e], PreventNullValues = (true|false))]\n    partial class [\u003cname\u003e]\n    {\n        // 0 or more fields per case\n        /// \u003csummary\u003e\n        /// [\u003cvalue description\u003e]\n        /// \u003c/summary\u003e       \n        [StructCase([\u003ccase1name\u003e], isDefaultValue: (true|false), description: [\u003ccase description\u003e])]        \n        readonly [type] [\u003ccase1Value\u003e];\n\n        // Parameterless cases. 0 or more attribute declaration, one for each case\n        [StructCase([\u003ccase2name\u003e], description: [\u003cdescription\u003e])]        \n        static partial class Cases\n        {          \n        }\n    }\n```\n\n## Case values\n[Case values]: #case-values\nA case may contain 0 or more fields that represent its value.\nWhen a case does not contain any, there is just one single possible value for this case. This single value is represented as a static field in the class.\n\n```csharp\n    [GenerateDiscriminatedUnion]\n    public class Unit\n    {\n        static partial class Cases\n        {\n            partial class Default : Unit { }\n        }\n    }\n    \n    var unit = Unit.Default;\n```\n\nWhen a case has one or more values, a factory method is generated. It takes a value as an argument for each readonly field in the case class.\n\n```csharp\n    [GenerateDiscriminatedUnion]\n    public class Mammal\n    {\n        static partial class Cases\n        {\n            partial class Human : Mammal\n            {\n                readonly string firstName;\n                readonly string lastName;\n            }\n            \n            partial class Dog : Mammal\n            {\n                readonly string name;\n            }\n        }\n    }\n    \n    var human = Mammal.NewHuman(\"Alan\", \"Turing\");\n    var dog = Mammal.NewDog(\"Bob\");\n```\n\n## Using a struct\n[Using a struct]: #using-a-struct\nYou can also declare a discriminated union type as a struct.\nThe declaration is slightly different, because its internal representation is different. Each parameter must be declared as a private field of the type, and must be linked to the case with the `StructCase` attribute.\nYou must also use the `StructCase` attribute to declare cases that do not use parameters.\nIn addition, if you can specifiy which case is the default value of the struct (`var mammal = default(Mammal);`).\n\nHere is an example: \n\n```csharp\n    [GenerateDiscriminatedUnion]\n    public struct Mammal\n    {\n        [StructCase(\"Human\")]\n        readonly string firstName;\n        [StructCase(\"Human\")]\n        readonly string lastName;\n        [StructCase(\"Dog\", isDefaultValue: true)]\n        readonly string name;\n\n        [StructCase(\"Cow\")]\n        static partial class Cases\n        {\n        }\n    }\n    \n    var human = Mammal.NewHuman(\"Alan\", \"Turing\");\n    var dog = Mammal.NewDog(\"Bob\");\n    var cow = Mammal.Cow;\n    var mammal = default(Mammal); // it is a dog. Note that the name property will be null;    \n```\n\n## Equality\n[Equality]: #equality\nUnion types represent a value, therefore if a case have values, all those values are considered in the equality comparison.\n2 instances that represent a different case cannot be equal.\nIf 2 instances represent the same case, their values are compared using the Equals method.\nHere are some examples:\n\n```csharp \n    Mammal.NewHuman(\"Alan\", \"Turing\") == Mammal.NewHuman(\"Alan\", \"Turing\"); // returns true\n    Mammal.NewHuman(\"Alan\", \"Turing\") != Mammal.NewHuman(\"Alan\", \"Turing\"); // returns false\n    Mammal.NewHuman(\"Alan\", \"Turing\") == Mammal.NewHuman(\"Alan\", \"Stivell\"); // returns false    \n    Mammal.NewHuman(\"Alan\", \"Turing\") == Mammal.NewDog(\"Bob\"); // returns false\n```\n\n## Match method\n[Match method]: #match-method\nA method named `Match` is generated.\nWhen using it, you should provide 1 function for each possible case of the type. This allows to make sure that every possible case can be handled by the code using it.\nHere are some examples:\n\n```csharp\n    string IsHuman(Mammal mammal) =\u003e \n    {\n        Match(\n          (firstName, lastName) =\u003e true,\n          name =\u003e false,\n          name =\u003e false\n          ));    \n    }\n    IsHuman(Mammal.NewHuman(\"Alan\", \"Turing\")); //return true\n    IsHuman(Mammal.NewDog(\"Bob\")); //return false\n    IsHuman(Mammal.NewCat(\"Bob\")); //return false    \n```\n\nAlternatively, you can use an overload that does not requires to provide a function for all the cases.\n\n```csharp\n    string IsHuman(Mammal mammal) =\u003e \n    {\n        Match(\n          () =\u003e false,\n          matchHuman: (firstName, lastName) =\u003e true\n          ));    \n    }\n    IsHuman(Mammal.NewHuman(\"Alan\", \"Turing\")); //return true\n    IsHuman(Mammal.NewDog(\"Bob\")); //return false\n    IsHuman(Mammal.NewCat(\"Bob\")); //return false    \n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgalad%2Fcsharpdiscriminatedunion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgalad%2Fcsharpdiscriminatedunion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgalad%2Fcsharpdiscriminatedunion/lists"}