AnAspect.OneOf
0.1.1-alpha
dotnet add package AnAspect.OneOf --version 0.1.1-alpha
NuGet\Install-Package AnAspect.OneOf -Version 0.1.1-alpha
<PackageReference Include="AnAspect.OneOf" Version="0.1.1-alpha"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="AnAspect.OneOf" Version="0.1.1-alpha" />
<PackageReference Include="AnAspect.OneOf"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add AnAspect.OneOf --version 0.1.1-alpha
#r "nuget: AnAspect.OneOf, 0.1.1-alpha"
#:package AnAspect.OneOf@0.1.1-alpha
#addin nuget:?package=AnAspect.OneOf&version=0.1.1-alpha&prerelease
#tool nuget:?package=AnAspect.OneOf&version=0.1.1-alpha&prerelease
AnAspect.OneOf - IL-Weaved Discriminated Unions
A high-performance discriminated union library for C# that uses IL weaving (Fody) to eliminate memory overhead while maintaining full type safety at compile time.
๐ Key Innovation
Traditional OneOf implementations use structs or classes with discriminators, causing significant memory overhead. This library provides the same developer experience but rewrites the IL at build time to use plain object references, achieving:
- 30-57% memory reduction compared to traditional OneOf implementations
- Zero runtime overhead for type checking (direct
isoperator) - Full IntelliSense support at development time
- Type safety maintained at compile time
๐ Benchmark Results (.NET 10)
Test 1: Traditional OneOf<User, Admin> (reference types)
Allocated: 10,413,984 bytes (104.00 bytes per iteration)
Per OneOf wrapper: ~40.00 bytes
Test 2: IL-Weaved OneOf<User, Admin> (object reference only)
Allocated: 7,199,992 bytes (71.00 bytes per iteration)
Per OneOf wrapper: ~7.00 bytes
Memory Savings: 30.9% reduction
Test 3: Traditional OneOf<Point, int> (value types)
Allocated: 5,601,560 bytes (56.00 bytes per iteration)
Test 4: IL-Weaved OneOf<Point, int> (value types)
Allocated: 2,402,840 bytes (24.00 bytes per iteration)
Memory Savings: 57.1% reduction
๐ฏ How It Works
At Compile Time (What You Write):
OneOf<int, string> value = OneOf<int, string>.From(42);
if (value.IsT1)
{
int result = value.AsT1();
Console.WriteLine(result);
}
After IL Weaving (What Gets Executed):
object value = (object)42; // Direct boxing
if (value is int) // Direct type check
{
int result = (int)value; // Direct cast
Console.WriteLine(result);
}
The weaver automatically:
- โ
Retargets local variables from
OneOf<T1,T2>โobject - โ
Replaces
From(value)with direct value (boxed if value type) - โ
Replaces
IsT1withvalue is T1 - โ
Replaces
AsT1()with(T1)value
๐ฆ Installation
Simple - Just 2 packages:
<ItemGroup>
<PackageReference Include="Fody" Version="6.8.0" PrivateAssets="all" />
<PackageReference Include="AnAspect.OneOf" Version="0.1.1-alpha" />
</ItemGroup>
Note: The main package automatically includes the source generator and attributes as dependencies.
2. Add FodyWeavers.xml
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<OneOf />
</Weavers>
3. Use OneOf
using OneOf;
// Reference types (optimal - no boxing)
OneOf<User, Admin> user = OneOf<User, Admin>.From(new User("Alice", 30));
if (user.IsT1)
{
var u = user.AsT1();
Console.WriteLine($"User: {u.Name}, Age: {u.Age}");
}
// Value types (boxing required, but still efficient)
OneOf<int, string> value = OneOf<int, string>.From(42);
if (value.IsT1)
{
Console.WriteLine($"Got int: {value.AsT1()}");
}
// Mixed types
OneOf<Point, string> mixed = OneOf<Point, string>.From(new Point(10, 20));
if (mixed.IsT1)
{
var p = mixed.AsT1();
Console.WriteLine($"Point: ({p.X}, {p.Y})");
}
โก Performance Characteristics
Memory Usage:
- Reference types: 1 object reference (8 bytes on x64) - OPTIMAL
- Value types: 1 object reference + boxing overhead - acceptable trade-off
- Discriminator overhead: ELIMINATED (0 bytes vs 1-8 bytes in traditional)
Performance:
- Type checking: Direct
isoperator (no discriminator comparison) - Value extraction: Direct cast (no wrapper unwrapping)
- Creation: Direct reference/box (no wrapper allocation for ref types)
๐๏ธ Architecture
Components:
- OneOf.Attributes: Marker attribute for Fody detection
- OneOf.SourceGenerator: Generates
OneOf<T1,T2>class at compile time - OneOf.Fody: IL weaver that transforms the generated code
- OneOf.Analyzers (TODO): Compile-time warnings and best practices
Supported Patterns:
- โ
From(T)- Create OneOf from value - โ
IsT1,IsT2- Type checking - โ
AsT1(),AsT2()- Value extraction with exception on mismatch - โ Implicit conversions from T1/T2
- โ ๏ธ
TryGetT1(out T)- Not yet optimized (use Is/As pattern instead)
๐ Best Practices
DO:
- โ Use reference types in OneOf when possible (zero boxing overhead)
- โ Use Is/As pattern for best performance
- โ Store OneOf in local variables for optimal weaving
AVOID:
- โ ๏ธ Don't use
TryGetmethods (not yet weaved - use Is/As instead) - โ ๏ธ Be aware of boxing overhead with value types
- โ ๏ธ Debugger shows
objectafter weaving (notOneOf<T1,T2>)
๐ Current Limitations
- Only
OneOf<T1, T2>supported (T3, T4, etc. TODO) - TryGet methods not yet rewritten (use Is/As pattern)
- Fields/parameters/returns not yet retargeted to object (locals only)
- No custom serializer support yet
๐ฌ Technical Details
IL Transformation Examples:
Before:
ldloc.0 // Load OneOf<int,string> local
call get_IsT1 // Call IsT1 property
brfalse.s IL_000d
ldloc.0
call AsT1 // Call AsT1 method
After:
ldloc.0 // Load object local
isinst int32 // Direct type check
brfalse.s IL_000d
ldloc.0
unbox.any int32 // Direct unbox
๐งช Testing
Run tests:
dotnet test tests/OneOf.Tests/OneOf.Tests.csproj
Run benchmarks:
dotnet run --project tests/OneOf.Benchmarks/OneOf.Benchmarks.csproj -c Release -- --manual
๐ License
MIT License - See LICENSE file for details.
๐ค Contributing
Contributions welcome! See CONTRIBUTING.md for guidelines.
GitHub: https://github.com/Pouria7/AnAspect.OneOf
๐ฏ Roadmap
- Support OneOf<T1, T2, T3, ..., T8>
- Implement TryGet IL rewriting
- Retarget fields/parameters/returns to object
- Custom serialization support
- Roslyn analyzers for best practices
- NuGet packaging
- Documentation site
โ ๏ธ Disclaimer
This is an experimental project demonstrating advanced IL weaving techniques. Use in production at your own risk. The trade-off is:
- Gain: Significant memory savings, zero-cost abstractions
- Cost: Boxing for value types, IL weaving complexity, debugger experience
For most applications, the memory savings and performance gains make this trade-off worthwhile, especially when using reference types.
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- FodyHelpers (>= 6.8.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.1-alpha | 137 | 12/13/2025 |
| 0.1.0-alpha | 128 | 12/13/2025 |