diff --git a/README.md b/README.md
index 5fbb927..dbef4c3 100644
--- a/README.md
+++ b/README.md
@@ -2,37 +2,70 @@
[](https://github.com/mattwar/UnionTypes.Toolkit/actions/workflows/ci.yml)
[](https://github.com/mattwar/UnionTypes.Toolkit/releases/latest)
-[](https://www.nuget.org/packages/UnionTypes.Toolkit.Generator)
-This repo contains a library implementing some common custom union types compatible with the C# union type feature and a source generator that generates custom C# union types that uses techniques to avoid boxing and minimize memory footprint.
+This repo is the source beind two packages available on Nuget.
-This library was originally one of many similar libraries I created when designing and working on the C# Union Types feature. It has now been refitted to match the shipping design and the generator reimplemented to be more robust.
+- [UnionTypes.Toolkit](#uniontypestoolkit) - a library of common custom union types
+ [](https://www.nuget.org/packages/UnionTypes.Toolkit.Generator)
-Please report bugs here under issues or submit PRs to fix them if you prefer.
+- [UnionTypes.Toolkit.Generator](#uniontypestoolkitgenerator) - a source generator for non-boxing custom unions
+ [](https://www.nuget.org/packages/UnionTypes.Toolkit)
+
+
+### What is this?
+
+This repo was originally created as a design playground when working on the C# Union Types feature. It was meant to contain various hand built union types with a variety of implementation strategies and a general configurable source generator that would produce custom unions using the techniques being discussed during design meetings.
+This was long before any actual design was settled on and language feature worked started.
+
+The original nuget release years ago contained a very different concept of unions to what has now become a feature for C# 15.
+
+Both the toolkit library and the source generator have been updated to match the new C# Union Types feature, and both are now available for general use.
+
+The source generator has been retrofitted to produce only unions matching the specification for C# Union Types feature and using the Custom Unions API's.
+The generator produces unions that do not box struct values by default.
+
+### How to Contribute
+
+Please report bugs here under issues or submit PRs to fix them if you prefer.
Use discussions instead of issues to share ideas or make requests.
+----
+
-## The UnionTypes.Toolkit Library
+## UnionTypes.Toolkit
-**This is a work in progress**
+A collection of common union types compatible with the C# Union Types feature.
-It currently includes implementations of Option, Result and a family of generic 'boxed' unions, for use when you don't need the formality of inventing a new named union. There is also a family of non-boxing (fat) generic unions for when you don't like boxing and are not concerned with memory footprint.
+**Option**
-*This library is not yet published to NuGet, but can be accessed from releases on GitHub.*
+The `Option` union type allows you to represent either `Some` or `None` without boxing.
+**Result**
-## The UnionTypes.Toolkit.Generator Library
+The `Result` union type allows you to represent either `Success` or `Failure` without boxing.
-This library implements a C# source generator for generating non-boxing custom union types compatible with the C# union types feature. It may contain additional generators in the future.
+**Union**
-The generator is purely standalone; the generated union source does not depend on the union type library or any other external library beyond the standard dotnet runtime to function.
+A family of generic union types `Union`, `Union`, etc.
+
+You can use these without declaring a unique named union type.
+The held value will be boxed, however.
+
+**FatUnion**
-### Download the Generator
+A family of generic union types `FatUnion`, `FatUnion`, etc.
-The generator is available as a nuget package, or can be accessed from release builds here on GitHub.
+You can use these without declaring a unique named union type.
+The held value will not be boxed, but the type requires a memory footprint similar to using a tuple.
-### [Download from Nuget Here](https://www.nuget.org/packages/UnionTypes.Toolkit.Generator)
+----
+
+## UnionTypes.Toolkit.Generator
+
+This library implements a C# source generator for generating non-boxing custom union types compatible with the C# union types feature. It may contain additional generators in the future.
+
+The generator is purely standalone; the generated union source does not depend on the union type library or any other external library beyond the standard dotnet runtime to function.
## Declaring a Non-Boxing Custom Union Type
diff --git a/src/SourceGenerators.Package/ReadMe.Nuget.md b/src/SourceGenerators.Package/ReadMe.Nuget.md
index 76d8165..ec23215 100644
--- a/src/SourceGenerators.Package/ReadMe.Nuget.md
+++ b/src/SourceGenerators.Package/ReadMe.Nuget.md
@@ -1,4 +1,4 @@
-# UnionTypes.Toolkit.Generators
+# UnionTypes.Toolkit.Generator
A C# source generator library for generating custom union types compatible with the C# union types feature.
@@ -35,4 +35,4 @@ The generator will layout the contents of the custom union so that the fields st
In this example, there will be a single field storing a struct that contains enough space to store either an int, float, Coordinate or the address Id and a sparate object field used to store either a string, IManifest or the address Name.
-# [Learn how to customize the union generation further](https://github.com/mattwar/UnionTypes.Toolkit)
+[Learn how to customize the union generation further](https://github.com/mattwar/UnionTypes.Toolkit)
diff --git a/src/UnionTypes.Tests/ResultTests.cs b/src/UnionTypes.Tests/ResultTests.cs
index bab89a1..62d47dd 100644
--- a/src/UnionTypes.Tests/ResultTests.cs
+++ b/src/UnionTypes.Tests/ResultTests.cs
@@ -26,7 +26,7 @@ public void Test_Default_HasNoValue()
{
Result result = default;
Assert.IsFalse(result.HasValue);
- Assert.IsTrue(result.Value is null);
+ Assert.ThrowsException(() => { var value = result.Value; });
}
[TestMethod]
diff --git a/src/UnionTypes/Option.cs b/src/UnionTypes/Option.cs
index e33d5b3..18889ab 100644
--- a/src/UnionTypes/Option.cs
+++ b/src/UnionTypes/Option.cs
@@ -1,44 +1,36 @@
namespace UnionTypes.Toolkit;
+///
+/// A union that may contain either a value or a value.
+/// It is similar to , except the value can be also be a reference type and may use null as a valid value.
+///
[System.Runtime.CompilerServices.Union]
public struct Option
: System.Runtime.CompilerServices.IUnion
{
- private readonly object? _value;
-
- private static readonly bool _isNoneType = typeof(T) == typeof(None);
+ private readonly T _value;
+ private readonly bool _hasValue;
public Option(Some value)
{
- if (_isNoneType)
- {
- // some cheeky user has used the None type as the value type.
- _value = _someOfNoneBoxed;
- }
- else
- {
- _value = value.Value;
- }
+ _value = value.Value;
+ _hasValue = true;
}
public Option(None value)
{
// store None as null, so it matches the same state as when the struct is default-initialized.
- _value = null;
+ _value = default!;
+ _hasValue = false;
}
- public bool HasValue => false; // we return None if null, so HasValue is always false
+ public bool HasValue => true; // always has either some or none, so this is always true.
public bool TryGetValue(out Some value)
{
- if (_value is T val)
- {
- value = new Some(val);
- return true;
- }
- else if (_value is Some someValue)
+ if (_hasValue)
{
- value = someValue;
+ value = new Some(_value);
return true;
}
else
@@ -50,9 +42,9 @@ public bool TryGetValue(out Some value)
public bool TryGetValue(out None value)
{
- if (_value == null)
+ if (!_hasValue)
{
- value = Option.None;
+ value = new None();
return true;
}
else
@@ -62,17 +54,10 @@ public bool TryGetValue(out None value)
}
}
- public object Value => _value switch
- {
- null => _noneBoxed,
- T val => val,
- _ => _value
- };
-
- public static implicit operator Option(T value) => new Option(new Some(value));
+ public object Value =>
+ _hasValue ? new Some(_value) : new None();
- private readonly object _noneBoxed = new None();
- private readonly object _someOfNoneBoxed = new Some(new None());
+ public static implicit operator Option(T value) => new Option(new Some(value));
}
///
diff --git a/src/UnionTypes/ReadMe.Nuget.md b/src/UnionTypes/ReadMe.Nuget.md
index 5043bb2..3245326 100644
--- a/src/UnionTypes/ReadMe.Nuget.md
+++ b/src/UnionTypes/ReadMe.Nuget.md
@@ -1,5 +1,25 @@
# UnionTypes.Toolkit
-A collection of common union types and tools for building custom ones.
+A collection of common union types compatible with the C# Union Types feature.
+
+**Option**
+
+The `Option` union type allows you to represent either `Some` or `None`.
+
+**Result**
+
+The `Result` union type allows you to represent either `Success` or `Failure`.
+
+**Union**
+
+A family of generic union types `Union`, `Union`, etc.
+You can use these without declaring a unique named union type.
+The held value will be boxed, however.
+
+**FatUnion**
+
+A family of generic union types `FatUnion`, `FatUnion`, etc.
+You can use these without declaring a unique named union type.
+The held value will not be boxed, but the type has space for all cases, similar to a tuple.
[Learn about using the toolkit in your project here.](https://github.com/mattwar/UnionTypes.Toolkit)
diff --git a/src/UnionTypes/Result.cs b/src/UnionTypes/Result.cs
index c22553e..7f66b83 100644
--- a/src/UnionTypes/Result.cs
+++ b/src/UnionTypes/Result.cs
@@ -3,67 +3,38 @@
namespace UnionTypes.Toolkit;
+///
+/// A union that may contain either a value or a value.
+///
[System.Runtime.CompilerServices.Union]
public struct Result
: System.Runtime.CompilerServices.IUnion
{
- private readonly object? _value;
-
- private static readonly bool _typeArgsMatch =
- typeof(TValue).IsAssignableTo(typeof(TError))
- || typeof(TError).IsAssignableFrom(typeof(TValue));
+ private readonly byte _kind;
+ private readonly TValue _value;
+ private readonly TError _error;
public Result(Success value)
{
- if (_typeArgsMatch)
- {
- // both TValue and TError have intersecting types, so we must store the value as the Success boxed.
- _value = value;
- }
- else if (value.Value == null)
- {
- // the success value is itself null, use the pre-boxed default.
- _value = _successDefaultBoxed;
- }
- else
- {
- // Store the non-null success value itself.
- // This does not incur boxing of the Success struct, but it does incur boxing of the TValue value if it is a value type.
- _value = value.Value;
- }
+ _kind = 1;
+ _value = value.Value;
+ _error = default!;
}
public Result(Failure value)
{
- if (_typeArgsMatch)
- {
- // both TValue and TError have intersecting types, so we must store the value as the Failure boxed.
- _value = value;
- }
- else if (value.Error == null)
- {
- _value = _failureDefaultBoxed;
- }
- else
- {
- // Store the failure value itself.
- // This does not incur boxing of the Failure struct, but it does incur boxing of the TError value if it is a value type.
- _value = value.Error;
- }
+ _kind = 2;
+ _value = default!;
+ _error = value.Error;
}
- public bool HasValue => _value != null;
+ public bool HasValue => _kind != 0; // has no value if uninitialized, otherwise has either success or failure.
public bool TryGetValue(out Success value)
{
- if (_value is TValue val)
- {
- value = new Success(val);
- return true;
- }
- else if (_value is Success successValue)
+ if (_kind == 1)
{
- value = successValue;
+ value = new Success(_value);
return true;
}
else
@@ -75,14 +46,9 @@ public bool TryGetValue(out Success value)
public bool TryGetValue(out Failure value)
{
- if (_value is TError err)
- {
- value = new Failure(err);
- return true;
- }
- else if (_value is Failure failureValue)
+ if (_kind == 2)
{
- value = failureValue;
+ value = new Failure(_error);
return true;
}
else
@@ -92,29 +58,40 @@ public bool TryGetValue(out Failure value)
}
}
- public object? Value => _value switch
+ public object Value => _kind switch
{
- TValue val => new Success(val),
- TError err => new Failure(err),
- Success succ => succ,
- Failure fail => fail,
- _ => null
+ 1 => new Success(_value),
+ 2 => new Failure(_error),
+ _ => throw new System.InvalidOperationException("Result is uninitialized and has no value.")
};
public static implicit operator Result(TValue value) => new Result(new Success(value));
public static implicit operator Result(TError error) => new Result(new Failure(error));
-
- private readonly object _successDefaultBoxed = new Success(default!);
- private readonly object _failureDefaultBoxed = new Failure(default!);
}
-
+///
+/// Represents a successful result in a Result union.
+///
public record struct Success(T Value);
+
+///
+/// Represents a failed result in a Result union.
+///
public record struct Failure(T Error);
+///
+/// Helper class for creating Result union instances.
+///
public static class Result
{
+ ///
+ /// Creates a instance with the specified value.
+ ///
public static Success Success(TValue value) => new Success(value);
+
+ ///
+ /// Creates a instance with the specified error.
+ ///
public static Failure Failure(TError error) => new Failure(error);
}
\ No newline at end of file