UnionSupport.Analyzer
1.0.3
dotnet add package UnionSupport.Analyzer --version 1.0.3
NuGet\Install-Package UnionSupport.Analyzer -Version 1.0.3
<PackageReference Include="UnionSupport.Analyzer" Version="1.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="UnionSupport.Analyzer" Version="1.0.3" />
<PackageReference Include="UnionSupport.Analyzer"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add UnionSupport.Analyzer --version 1.0.3
#r "nuget: UnionSupport.Analyzer, 1.0.3"
#:package UnionSupport.Analyzer@1.0.3
#addin nuget:?package=UnionSupport.Analyzer&version=1.0.3
#tool nuget:?package=UnionSupport.Analyzer&version=1.0.3
UnionSupport
A C# Source Generator providing discriminated union implementations, aligned with the C# Language Proposal for Unions. <br> <sub>基于 C# Source Generator 的和类型实现,遵循 C# 语言官方提案标准。</sub>
Stop-Gap Solution: Once .NET ships native
struct union/union struct, a Code Fix will be provided to migrate seamlessly.
Spec Compliance
This project implements the union pattern as defined in the official proposal:
[Union]attribute marks a type as a union typeIUnion { object? Value { get; } }interface (System.Runtime.CompilerServices)- Union conversion: implicit conversions from each case type to the union
- Union matching: pattern matching unwraps
IUnion.Valueautomatically - Non-boxing access pattern:
HasValue+ per-caseTryGetValue(out T)overloads
Nullable Value Type Unwrapping
Per the proposal: "If the case type is a nullable value type, the type of the parameter should be identity-convertible to the underlying type."
TryGetValue and implicit operators strip ? from nullable value type members — int? becomes int:
[UnionImpl] partial struct MyUnion(int? a, float b);
MyUnion x = 42; // int → int (not int?)
x.TryGetValue(out int iv); // out int, not out int?
<sub>按提案标准:nullable 值类型穿透为底层类型,TryGetValue 和隐式转换使用展开后的类型。</sub>
Project Structure
UnionSupport.slnx
├── src/
│ ├── UnionSupport.Core/ IUnion, [UnionImpl], [Union], Strategy enum
│ ├── UnionSupport.Generator.Shared/ Shared code-gen + field name encoding
│ ├── UnionSupport.Generator.Product/ Strategy 1: Tagged product type
│ ├── UnionSupport.Generator.Unmanaged/Strategy 2: FieldOffset native sum type
│ ├── UnionSupport.Generator.Erasure/ Strategy 3: Object type erasure
│ ├── UnionSupport.Analyzer/ Compile-time diagnostics (UNION001-003)
│ └── UnionSupport.Type/ Pre-generated unions for 1-17 type params
├── tests/ xUnit + Verify snapshot tests
└── demo/ConsoleApp1/ Usage examples
NuGet Packages
| Package | Description |
|---|---|
UnionSupport.Core |
Core types — required |
UnionSupport.Generator.Product |
Tagged product type generator |
UnionSupport.Generator.Unmanaged |
FieldOffset native sum type generator |
UnionSupport.Generator.Erasure |
Object type erasure generator |
UnionSupport.Analyzer |
Compile-time analyzer |
UnionSupport.Type |
Pre-generated union types (no generator needed) |
Quick Start
Custom Unions
[UnionImpl] defaults to Product strategy.
using UnionSupport;
// Product (default, general purpose)
[UnionImpl]
partial struct MyUnion(int a, float b, string c);
// Unmanaged FieldOffset (high perf, unmanaged types only)
[UnionImpl(UnionImplementationStrategy.Unmanaged)]
partial struct IntOrFloat(int a, float b);
// Object Erasure (single object field, C# native)
[UnionImpl(UnionImplementationStrategy.ObjectErasure)]
partial struct AnyValue(int a, string b)
{
/*not Value,but it's OK to use Value if you don't mind boxing*/
public string Unwrap()=> this switch
{
string i => i,
_ => throw new Exception()
}
};
// ref struct (Product only)
[UnionImpl]
ref partial struct SpanUnion(int a, Span<byte> b);
Usage
// Implicit conversions
MyUnion x = 42;
IntOrFloat y = 3.14f;
AnyValue z = "hello";
// Pattern matching (compiler auto-unwraps)
switch (x)
{
case int i: Console.WriteLine($"int: {i}"); break;
case float f: Console.WriteLine($"float: {f}"); break;
case string s: Console.WriteLine($"string: {s}"); break;
}
// Direct access
Console.WriteLine(x.Value); // if you use ref struct as a case,you cannot do like this
Console.WriteLine(x.HasValue);
Pre-generated Generic Types
Add UnionSupport.Type package:
Union<int, float, string> u = 42; // Product
CUnion<int, float> cu = 3.14f; // Unmanaged (T : unmanaged)
BoxedUnion<int, string> bu = "hi"; // Object Erasure
switch (u)
{
case int i: ... break;
case float f: ... break;
case string s: ... break;
}
Duplicate type parameters (e.g.
Union<int, int>) are caught at instantiation by the C# compiler itself — duplicateTryGetValue(out int)signatures cause CS0111.
Three Strategies
| Product | Unmanaged | ObjectErasure | |
|---|---|---|---|
| Storage | Separate fields + byte tag |
FieldOffset overlapping | Single object? field |
| Layout | Compiler-managed | [StructLayout(Explicit)] |
Regular |
| Memory | Sum of field sizes | Largest field + 1 byte | Object reference |
| Boxing | None | None | Value types boxed |
| Generic constraint | None | T : unmanaged |
None |
| Use case | General purpose | High-perf, interop | Simple ref/value mix |
| ref struct | ✅ | ❌ | ❌ |
| Field init | = paramName |
Constructor body | Constructor body |
Generated Code (Product)
partial struct MyUnion : IUnion
{
private byte __private_flag;
private int __System_Int32 = a; // field initializer from primary ctor param
private float __System_Single = b; // → CS9113 eliminated
public object? Value { get { return __private_flag switch { 1 => __System_Int32, 2 => __System_Single, _ => null }; } }
public bool HasValue => __private_flag != 0;
public bool TryGetValue(out int value) { if (__private_flag == 1) { value = __System_Int32; return true; } ... }
public MyUnion(int value) : this(value, default!) { __private_flag = 1; }
public MyUnion(float value) : this(default!, value) { __private_flag = 2; }
public static implicit operator MyUnion(int value) => new(value);
public static implicit operator MyUnion(float value) => new(value);
}
Analyzer Rules
| ID | Rule |
|---|---|
| UNION001 | Duplicate type: (int, int) / (T, T) |
| UNION002 | Ref struct constraint: must use Product / ref member on non-ref struct |
| UNION003 | Unmanaged strategy requires !IsUnmanagedType — no managed types |
Disclaimer
This is a Vibe Coding project — a stop-gap for the absence of native
struct unionin C#. <br><sub>本项目是 Vibe Coding 产物,是 .NET 官方struct union的过渡替代方案。</sub>
The C# language team has drafted the full union specification in csharplang/proposals/unions.md. Once the compiler ships native union struct / struct union support:
- A Code Fix will migrate
[UnionImpl(Product)] partial struct Foo(...)→union struct Foo(...)seamlessly. - A proof-of-concept Code Fix demo for
Product → struct union/Product → union structalready exists. - We will eliminate compile-time code generation overhead and switch to compiler-native support.
- Migration tooling will be provided for existing projects.
Contributions welcome. If you're interested in the migration work or have ideas, join the discussion at Issues.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.