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.
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
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
OneOf<int, string> value = OneOf<int, string>.From(42);
if (value.IsT1)
{
int result = value.AsT1();
Console.WriteLine(result);
}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
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.
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<AnAspect.OneOf />
</Weavers>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})");
}- 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)
- Type checking: Direct
isoperator (no discriminator comparison) - Value extraction: Direct cast (no wrapper unwrapping)
- Creation: Direct reference/box (no wrapper allocation for ref types)
- 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
- ✅
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)
- ✅ 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
⚠️ Don't useTryGetmethods (not yet weaved - use Is/As instead)⚠️ Be aware of boxing overhead with value types⚠️ Debugger showsobjectafter weaving (notOneOf<T1,T2>)
- 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
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
Run tests:
dotnet test tests/OneOf.Tests/OneOf.Tests.csprojRun benchmarks:
dotnet run --project tests/OneOf.Benchmarks/OneOf.Benchmarks.csproj -c Release -- --manualMIT License - See LICENSE file for details.
Contributions welcome! See CONTRIBUTING.md for guidelines.
GitHub: https://github.com/Pouria7/AnAspect.OneOf
- 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
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.