Skip to content

Type-safe, zero-allocation discriminated unions for modern .NET Bring TypeScript-style union types to C# — safer than object, cleaner than if, and built for real-world APIs and models.

License

Notifications You must be signed in to change notification settings

anzawi/MultiType.NET

Repository files navigation

🚦 MultiType.NET

Type-safe, zero-allocation discriminated unions for modern .NET
Bring TypeScript-style union types to C# — safer than object, cleaner than if, and built for real-world APIs and models.

Any<int, string> result = "hello";

NuGet Downloads


🧠 What Is a Union Type?

A union type lets a value be one of multiple specified types — like this in TypeScript:

let result = number | string | null

// C# with MultiType.NET
Any<int, string> result = "hello";

MultiType.NET brings that idea to C# with full support for:

  • ✅ Type-safe access (Is<T>(), As<T>(), Match)
  • ✅ JSON (de)serialization
  • ✅ Zero boxing
  • ✅ ASP.NET API compatibility
  • ✅ Source generation for no-boilerplate code

✨ Features Overview

Category Features
✅ Matching Match, TryMatch, Switch, SwitchAsync, SwitchOrDefault
🔁 Mapping Map, MapAsync, MapSafe, MapValue, MapAny, MapWithContext, MapOrDefault, MapWhere
🔍 Selection Select, TrySelect, SelectAsync, SelectAsyncOrDefault, SelectWithContext, SelectWhere
📦 Extraction GetTn, TryGetTn, GetTn(out remainder), Deconstruct(...)
🧠 Introspection ActualType, AllowedTypes, IsNull, ToString()
⚙️ Construction From, TryFrom, FromTn, implicit/explicit operators
📤 Serialization Native System.Text.Json support (global or attribute-based)
🧑‍💻 API-Ready Works with Controllers & Minimal APIs
🧩 Generator Auto-generates union types via [GenerateAny] or Any<T1..Tn>
🧩 Model Binding All types are Controller / Minimal-API Binding Ready

🚀 Get Started

📦 Install Core Library

dotnet add package MultiType.NET.Core

This gives you:

  • Any<T1..T16> prebuilt types
  • Core features like Match, Map, Switch, TryMatch, ...etc
  • JSON support via converter factory

🔧 Add Optional Source Generators

Package Description
MultiType.NET.Generator Adds [GenerateAny(typeof(...))] support
MultiType.NET.SourceGenerator Enables Any<T1..Tn> (over 16 types), JSON support, API integration
dotnet add package MultiType.NET.Generator

This allow you to generate a custom types with [GenerateAny], for more details MultiType.NET.Generator attribute.

[GenerateAny(typeof(string), typeof(MyType))]
public partial struct MyCustomType{}

This will generate MyCustomType with all MultiType APIs.

Need to generate Any<T17, ..., Tn>?

Install the official CLI generator:

dotnet tool install --global MultiType.NET.SourceGenerator

Then run:

multitypegen --maxArity 50 --project PATH_TO_TARGET_PROJECT.csproj

for more details and documentation MultiType.NET.SourceGenerator CLI


💡 Learn by Example

Any<int, string, DateTime> result = "hello";

string output = result.Match(
    i => $"Int: {i}",
    s => $"String: {s}",
    d => $"Date: {d:yyyy-MM-dd}"
);
// Using generic Any<T1, T2, T3> directly from body
[HttpPost("process-multi-type")]
public async Task<IActionResult> ProcessMultiTypeData(
    [FromBody] Any<int, string, DateTime> requestData)
{
    // Example usage: requestData.Match(...)
    return Ok();
}

// Using generic Any<T1, T2, T3> from query string (less typical, but possible)
[HttpGet("query-multi-type")]
public async Task<IActionResult> QueryMultiTypeData(
    [FromQuery] Any<int, string, DateTime> queryData)
{
    return Ok();
}
if (result.TryGetT1(out var i, out var remainder))
    Console.WriteLine($"Was int: {i}");
else
    Console.WriteLine($"Not an int: {remainder}");
var summary = result.Select(
    i => $"# {i}",
    s => s.ToUpperInvariant(),
    d => d.ToShortTimeString()
);

🧱 Creating Any Values

MultiType.NET offers multiple ways to construct union values.

From(...) — dynamic dispatch

object raw = 123;
var value = Any<int, string, DateTime>.From(raw);

💡 Throws if the value is not one of the allowed types.

TryFrom(...) — safe version

if (Any<int, string>.TryFrom(someValue, out var result))
{
    // Use result
}

FromTn(...) — type-specific creation

var a = Any<int, string, bool>.FromT1(42);
var b = Any<int, string, bool>.FromT2("hello");

💡 These are especially useful for code generation, dynamic input handling, or overload clarity.

✅ Implicit Operators

Any<int, string> v1 = 5;
Any<int, string> v2 = "done";

📦 JSON Serialization

MultiType.NET works seamlessly with System.Text.Json without any manual changes.

✅ Per-type registration

public readonly partial struct MyUnionType;

🧪 Example

var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(Any<int, string>.From(123), options);

💡 JSON output includes both the value and the represented type.


🧩 Custom Types with [GenerateAny]

⚠️ Requires MultiType.NET.Generator installed.

[GenerateAny(typeof(int), typeof(string), typeof(Guid))]
public partial struct Payload;
// POST endpoint accepting Payload from body
[HttpPost("submit-payload")]
public async Task<IActionResult> SubmitPayload(
    [FromBody] Payload payload)
{
    // payload.Match(...)
    return Ok();
}

// GET endpoint accepting Payload from query
[HttpGet("search")]
public async Task<IActionResult> SearchPayload(
    [FromQuery] Payload query)
{
    return Ok();
}
Payload payload = Guid.NewGuid();

payload.Match(
    i => Console.WriteLine($"Int: {i}"),
    s => Console.WriteLine($"Str: {s}"),
    g => Console.WriteLine($"Guid: {g}")
);

** More advanced Type**

[GenerateAny(typeof(Success), typeof(Warning), typeof(Error), typeof(Info))]
public partial struct StatusType
{
    public static StatusType From(string value) => value.ToLowerInvariant() switch
    {
        "success" => new Success(),
        "warning" => new Warning(),
        "error" => new Error(),
        "info" => new Info(),
        _ => throw new ArgumentException("Invalid status", nameof(value))
    };

    public bool IsSuccess => this.Is<Success>();
    public bool IsWarning => this.Is<Warning>();
    public bool IsError => this.Is<Error>();
    public bool IsInfo => this.Is<Info>();

    public readonly struct Success { }
    public readonly struct Warning { }
    public readonly struct Error { }
    public readonly struct Info { }
}

⚙️ How It Works (Behind the Scenes)

  • Any<T1, ..., Tn> is a readonly struct with internal value/ref handling.
  • Tracks active type via TypeIndex
  • Source generators:
    • Generate custom union types with all logic
    • Auto-wire JSON support
    • Add full method surface (Match, Map, Select, etc.)

🧪 Real-World Use Cases

Use Case Example
✅ API Results Any<SuccessDto, ErrorDto>
🧑‍⚖️ Workflow Results Any<Approved, Rejected, Escalated>
🧠 State Modeling Any<Draft, Submitted, Published>
🧾 Flexible Inputs Any<string, int, bool>
🔄 Retry Backoff Any<RetryLater, Fail, Success>

📁 Project Structure

Project Description
MultiType.NET.Core Runtime types and logic
MultiType.NET.Generator [GenerateAny] source generation
MultiType.NET.SourceGenerator JSON + Any<T1..Tn> generation

📘 Documentation

Resource Link
🔍 Advanced Features docs/AdvancedFeatures.md
🧠 Generator Guide docs/SourceGenerators.md
💡 Integration Tips docs/Integration.md

🙌 Contributing

Contributions, issues, and PRs are welcome!
See CONTRIBUTING.md to get started.


📄 License

MIT — LICENSE


Because type safety shouldn't be optional.

About

Type-safe, zero-allocation discriminated unions for modern .NET Bring TypeScript-style union types to C# — safer than object, cleaner than if, and built for real-world APIs and models.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages