https://github.com/altasoft/choice
AltaSoft.Choice provides the [Choice] attribute to define discriminated union types in C#, enabling source-generated, type-safe choice models.
https://github.com/altasoft/choice
Last synced: 2 months ago
JSON representation
AltaSoft.Choice provides the [Choice] attribute to define discriminated union types in C#, enabling source-generated, type-safe choice models.
- Host: GitHub
- URL: https://github.com/altasoft/choice
- Owner: altasoft
- License: mit
- Created: 2025-05-03T12:52:26.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-05-16T07:18:55.000Z (about 1 year ago)
- Last Synced: 2025-07-05T19:36:16.016Z (12 months ago)
- Language: C#
- Size: 2.68 MB
- Stars: 11
- Watchers: 4
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# AltaSoft.Choice
[](https://www.nuget.org/packages/AltaSoft.Choice)
[](https://www.nuget.org/packages/AltaSoft.Choice.Generator)
[](https://dotnet.microsoft.com)
**AltaSoft.ChoiceGenerator** is a lightweight C# source generator that allows you to define *choice types* (discriminated unions) with minimal syntax.
---
## ✨ Features
- Simple `[Choice]` attribute for defining alternatives
- Generates type-safe properties
- Supports XML and System.Text.Json serialization
- Includes `CreateAsXxx`, `Match`, and `Switch` methods
- Auto-generates enum for valid choice types
- Implicit conversion operators for easy usage
- Generates XmlSerializer
---
## 🛠️ Installation
Add the following NuGet packages to your project:
```xml
```
---
## ✅ Define Your Choice Type
Mark your class with `[Choice]` and define **partial nullable properties** :
```csharp
using AltaSoft.Choice;
namespace AltaSoft.ChoiceGenerator.Tests;
[Choice]
public sealed partial class Authorisation1Choice
{
///
/// Specifies the authorisation, in a coded form.
///
[XmlTag("Cd")]
[JsonPropertyName("cd")]
public partial Authorisation1Code? Code { get; set; }
///
/// Specifies the authorisation, in a free text form.
///
[XmlTag("Prtry")]
public partial Proprietary? Proprietary { get; set; }
}
```
---
## ⚙️ Generated Code
Below is the generated code for the example above:
```csharp
//------------------------------------------------------------------------------
//
// This code was generated by 'AltaSoft Choice.Generator'.
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
//
//------------------------------------------------------------------------------
#nullable enable
using AltaSoft.ChoiceGenerator.Tests;
using AltaSoft.Choice;
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;
namespace AltaSoft.ChoiceGenerator.Tests;
#pragma warning disable CS8774 // Member must have a non-null value when exiting.
#pragma warning disable CS0628 // New protected member declared in sealed type
public sealed partial record Authorisation1Choice
{
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public Authorisation1Choice()
{
}
///
/// Choice enum
///
[JsonIgnore]
[XmlIgnore]
public ChoiceOf ChoiceType { get; private set; }
private AltaSoft.ChoiceGenerator.Tests.Authorisation1Code? _code;
///
/// Specifies the authorisation, in a coded form.
///
[DisallowNull]
[XmlElement("Cd")]
public partial AltaSoft.ChoiceGenerator.Tests.Authorisation1Code? Code
{
get => _code;
set
{
_code = value ?? throw new InvalidOperationException("Choice value cannot be null");
_proprietary = null;
ChoiceType = ChoiceOf.Code;
}
}
private Proprietary? _proprietary;
///
/// Specifies the authorisation, in a free text form.
///
[DisallowNull]
[XmlElement("Prtry")]
public partial Proprietary? Proprietary
{
get => _proprietary;
set
{
_proprietary = value ?? throw new InvalidOperationException("Choice value cannot be null");
_code = null;
ChoiceType = ChoiceOf.Proprietary;
}
}
///
/// Creates a new instance and sets its value using the specified .
///
/// The value to assign to the created choice instance.
public static AltaSoft.ChoiceGenerator.Tests.Authorisation1Choice CreateAsCode(AltaSoft.ChoiceGenerator.Tests.Authorisation1Code value) => new () { Code = value };
///
/// Creates a new instance and sets its value using the specified .
///
/// The value to assign to the created choice instance.
public static AltaSoft.ChoiceGenerator.Tests.Authorisation1Choice CreateAsProprietary(Proprietary value) => new () { Proprietary = value };
///
/// Applies the appropriate function based on the current choice type
///
/// The return type of the provided match functions
/// Function to invoke if the choice is a value
/// Function to invoke if the choice is a value
public TResult Match(
Func matchCode,
Func matchProprietary)
{
return ChoiceType switch
{
ChoiceOf.Code => matchCode(Code!.Value),
ChoiceOf.Proprietary => matchProprietary(Proprietary!),
_ => throw new InvalidOperationException($"Invalid ChoiceType. '{ChoiceType}'")
};
}
///
/// Applies the appropriate Action based on the current choice type
///
/// Action to invoke if the choice is a value
/// Action to invoke if the choice is a value
public void Switch(
Action matchCode,
Action matchProprietary)
{
switch (ChoiceType)
{
case ChoiceOf.Code:
matchCode(Code!.Value);
return;
case ChoiceOf.Proprietary:
matchProprietary(Proprietary!);
return;
default:
throw new XmlException($"Invalid ChoiceType. '{ChoiceType}'");
}
}
///
/// Implicitly converts an to an .
///
/// The to convert.
///
/// instance representing the code.
///
public static implicit operator Authorisation1Choice(AltaSoft.ChoiceGenerator.Tests.Authorisation1Code value) => CreateAsCode(value);
///
/// Implicitly converts an to an .
///
/// The to convert.
///
/// instance representing the code.
///
public static implicit operator Authorisation1Choice(Proprietary value) => CreateAsProprietary(value);
///
/// Determines whether the property should be serialized.
///
///
/// true if has a value; otherwise, false.
///
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeCode() => Code.HasValue;
///
/// Choice enumeration
///
[XmlType("ChoiceOf.Authorisation1Choice")]
public enum ChoiceOf
{
///
/// Specifies the authorisation, in a coded form.
///
Code,
///
/// Specifies the authorisation, in a free text form.
///
Proprietary,
}
}
```
---
## 💡 Example Usage
### Creating with CreateAs methods
```csharp
var choice = Authorisation1Choice.CreateAsCode(Authorisation1Code.FileLevelAuthorisationSummary);
var result = choice.Match(
code => $"It's a code: {code}",
prop => $"It's proprietary: {prop.ToString()}"
);
choice.Switch(
code => Console.WriteLine($"String: {code}"),
prop => Console.WriteLine($"Number: {prop.ToString()}")
);
```
### Creating using implicit operators
if property types are distinct implicit operators are generated
```csharp
Authosiration1Choice choice = Authorisation1Code.FileLevelAuthorisationSummary;
```
## 📦 Projects
- `AltaSoft.Choice`
Contains the `[Choice]` marker attribute
- `AltaSoft.Choice.Generator`
Implements the source generator that produces boilerplate code
---
## 📄 License
This project is licensed under the [MIT License](LICENSE).