This repo is intended to be used for discussion and experimentation during design of discriminated unions in C# future.
-
UnionTypes
A project buildingUnionTypes.dll
containing type definitions, interfaces and custom attributes needed for projects to use and declare union types. -
Generators
A project defining the core generators for OneOf and custom union types. These generators can be used by T4 templates to pre-build union types. -
UnionSourceGenerator
A project that builds the Roslyn source generator for building custom union types. -
UnionSourceGenerator.Targets
A project that builds the nuget package "UnionSourceGeneratior_x.x.x.nupkg" that contains the source generator for custom union types. -
UnionTests
A project with tests for verifying the correctness of generating and using union types. -
TryUnions
A seperate projects with its own solutionTryUnions.sln
. This is intended to be used as a scratch pad for expirimenting with union types.
-
Open UnionTypes.sln in Visual Studio
-
Restore Nuget packages
-
Build solution (debug is okay)
-
Open TryUnions.sln
-
Fix references to point to UnionTypes.dll built by UnionTypes.sln
-
Open Manage Nuget packages for TryUnions project.
-
Create a package source (in settings gear) to point to build directory of UnionSourceGenerator_x.x.x.nupkg package.
-
Switch to new package source in drop down
-
Install UnionSourceGenerator_x.x.x.nupkg.
-
Open program.cs in TryUnions.sln
-
Mess about with defining union types
A series of overloaded versions of OneOf<...>
are predeclared in UnionTypes.dll
.
Use them to declare type unions of existing types only.
OneOf<int, string> intOrString = 5;
OneOf<int, double> intOrDouble = OneOf<int, double>.Convert(intOrString);
if (intOrString.Is<int>()) { }
if (intOrString.TryGet<int>(out var value)) { }
var value = (int)intOrString;
OneOf<int, string> intOrString = 5;
OneOf<int, string, double> intOrStringOrDouble = OneOf<int, string, double>.Convert(intOrstring);
OneOf<int, string> intValue = 5;
OneOf<int, string> stringValue = "five";
var areEqual = intValue == 5;
var areEqual2 = intValue == intValue;
var notEqual = intValue == stringValue;
OneOf<int, string> value1 = 5;
OneOf<string, int> value2 = 5;
var areEqual = value1.Equals(value2);
You can declare a new union type by declaring a partial struct type with a Union
attribute using one of the following methods.
The source generator will fill out the rest of the API for you.
All types listed in the UnionTypes
attribute declared on the union type becomes a type case of the type union.
[Union]
[UnionTypes(typeof(Cat), typeof(Dog), typeof(Bird)]
public partial struct CatDogBird
{
}
public record struct Cat(string name, CatState state);
public record struct Dog(string name, DogState state);
public record struct Bird(string name, BirdState state, string[] thingsItSays);
Any record type declared within the body of the union type declaration becomes a case type of a type union.
[Union]
public partial struct CatDogBird
{
public record struct Cat(string name, CatState state);
public record struct Dog(string name, DogState state);
public record struct Bird(string name, BirdState state, string[] thingsItSays);
}
Any partial factory method with the name "Create" declares a type case.
[Union]
public partial struct CatDogBird
{
public static partial CatDogBird Create(Cat cat);
public static partial CatDogBird Create(Dog dog);
public static partial CatDogBird Create(Bird bird);
}
public record struct Cat(string name, CatState state);
public record struct Dog(string name, DogState state);
public record struct Bird(string name, BirdState state, string[] thingsItSays);
All tag names listed in the UnionTags
attribute declared on the union type become parameterless tag cases of the tag union.
[Union]
[UnionTags("Cat", "Dog", "Bird")]
public partial struct CatDogBird
{
}
Any partial factory method with a unique name declares a tag case.
[Union]
public partial struct CatDogBird
{
public static partial CatDogBird Cat(string name, CatState state);
public static partial CatDogBird Dog(string name, DogState state, bool friendly);
public static partial CatDogBird Bird(string name, BirdState state, string[] thingsItSays);
}
For the following examples, the union type CatDogBird
is defined as a tag union.
[Union]
public partial struct CatDogBird
{
public static partial CatDogBird Cat(string name, CatState state);
public static partial CatDogBird Dog(string name, DogState state, bool friendly);
public static partial CatDogBird Bird(string name, BirdState state, string[] thingsItSays);
}
public enum CatState { Eating, Sleeping, Playing, Hunting, Annoyed }
public enum DogState { Eating, Sleeping, Playing }
public enum BirdState { Quiet, Chirping }
var animal = CatDogBird.Cat("Fluffy", CatState.Sleeping);
var animal = CatDogBird.Cat("Fluffy", CatState.Sleeping);
var isCatOrDog = animal.IsCat || animal.IsDog;
var animal = CatDogBird.Cat("Fluffy", CatState.Sleeping);
var isCat = animal.TryGetCat(out var name, out var state);
var animal = CatDogBird.Cat("Fluffy", CatState.Sleeping);
var (name, state) = animal.GetCat();
CatDogBird animal = CatDogBird.Cat("Fluffy", CatState.Sleeping);
var (name, state) = animal.AsCat();
CatDogBird first = CatDogBird.Cat("Fluffy", CatState.Sleeping);
CatDogBird second = CatDogBird.Dog("Snoopy", DogState.Eating, friendly: true);
var same = first == second;
For the following examples, the union type CatDogBird
is defined as a type union.
[Union]
public partial struct CatDogBird
{
public static partial CatDogBird Create(Cat cat);
public static partial CatDogBird Create(Dog dog);
public static partial CatDogBird Create(Bird bird);
}
public record struct Cat(string name, CatState state);
public record struct Dog(string name, DogState state);
public record struct Bird(string name, BirdState state, string[] thingsItSays);
var pet = CatDogBird.Create(new Cat("Fluffy", CatState.Sleeping));
CatDogBird pet = new Cat("Fluffy", CatState.Sleeping);
CatDogBird pet = new Cat("Fluffy", CatState.Sleeping);
var isCatOrDog = pet.Is<Cat>() || pet.Is<Dog>();
CatDogBird pet = new Cat("Fluffy", CatState.Sleeping);
var isCat = cat.TryGet<Cat>(out var cat);
CatDogBird pet = new Cat("Fluffy", CatState.Sleeping);
Cat cat = cat.Get<Cat>();
OneOf<Cat, Dog> catOrDog = new new Cat("Fluffy", CatState.Sleeping);
CatDogBird pet = CatDogBird.Convert(catOrDog);
CatDogBird cat = new Cat("Fluffy", CatState.Sleeping);
CatDogBird dog = new Dog("Snoopy", DogState.Eating, friendly: true);
var same = cat == dog;
CatDogBird cat = new Cat("Fluffy", CatState.Sleeping);
Dog dog = new Dog("Snoopy", DogState.Eating);
var same = cat == dog;
OneOf<Cat, Dog> catOrDog = new new Cat("Fluffy", CatState.Sleeping);
CatDogBird pet = new Cat("Fluffy", CatState.Sleeping);
var same = pet == catOrDog;