├── .gitignore ├── Generator ├── Functions.cs_ ├── Generator.csproj ├── Program.cs └── Program.cs._ ├── OneOf.Extended ├── Assembly.cs ├── Functions.cs ├── OneOf.Extended.csproj ├── OneOfBaseT10.generated.cs ├── OneOfBaseT11.generated.cs ├── OneOfBaseT12.generated.cs ├── OneOfBaseT13.generated.cs ├── OneOfBaseT14.generated.cs ├── OneOfBaseT15.generated.cs ├── OneOfBaseT16.generated.cs ├── OneOfBaseT17.generated.cs ├── OneOfBaseT18.generated.cs ├── OneOfBaseT19.generated.cs ├── OneOfBaseT20.generated.cs ├── OneOfBaseT21.generated.cs ├── OneOfBaseT22.generated.cs ├── OneOfBaseT23.generated.cs ├── OneOfBaseT24.generated.cs ├── OneOfBaseT25.generated.cs ├── OneOfBaseT26.generated.cs ├── OneOfBaseT27.generated.cs ├── OneOfBaseT28.generated.cs ├── OneOfBaseT29.generated.cs ├── OneOfBaseT30.generated.cs ├── OneOfBaseT31.generated.cs ├── OneOfBaseT9.generated.cs ├── OneOfT10.generated.cs ├── OneOfT11.generated.cs ├── OneOfT12.generated.cs ├── OneOfT13.generated.cs ├── OneOfT14.generated.cs ├── OneOfT15.generated.cs ├── OneOfT16.generated.cs ├── OneOfT17.generated.cs ├── OneOfT18.generated.cs ├── OneOfT19.generated.cs ├── OneOfT20.generated.cs ├── OneOfT21.generated.cs ├── OneOfT22.generated.cs ├── OneOfT23.generated.cs ├── OneOfT24.generated.cs ├── OneOfT25.generated.cs ├── OneOfT26.generated.cs ├── OneOfT27.generated.cs ├── OneOfT28.generated.cs ├── OneOfT29.generated.cs ├── OneOfT30.generated.cs ├── OneOfT31.generated.cs └── OneOfT9.generated.cs ├── OneOf.FSharp ├── CreateFile.linq ├── FsOneOf.fs └── OneOf.FSharp.fsproj ├── OneOf.SourceGenerator.AnalyzerTests ├── AnalyzerTests.cs └── OneOf.SourceGenerator.AnalyzerTests.csproj ├── OneOf.SourceGenerator.Tests ├── OneOf.SourceGenerator.Tests.csproj └── SourceGeneratorTests.cs ├── OneOf.SourceGenerator ├── GeneratorDiagnosticDescriptors.cs ├── OneOf.SourceGenerator.csproj ├── OneOfGenerator.cs └── tools │ ├── install.ps1 │ └── uninstall.ps1 ├── OneOf.Tests ├── BaseClassTests.cs ├── DefaultConstructorTests.cs ├── EqualsTests.cs ├── InterfaceTests.cs ├── MapTests.cs ├── MixedReferenceAndValueTypeTests.cs ├── OneOf.Tests.csproj ├── OneOfJsonConverter.cs ├── Serialization.cs └── ToStringTests.cs ├── OneOf.sln ├── OneOf ├── Assembly.cs ├── Functions.cs ├── IOneOf.cs ├── OneOf.csproj ├── OneOfBaseT0.generated.cs ├── OneOfBaseT1.generated.cs ├── OneOfBaseT2.generated.cs ├── OneOfBaseT3.generated.cs ├── OneOfBaseT4.generated.cs ├── OneOfBaseT5.generated.cs ├── OneOfBaseT6.generated.cs ├── OneOfBaseT7.generated.cs ├── OneOfBaseT8.generated.cs ├── OneOfT0.generated.cs ├── OneOfT1.generated.cs ├── OneOfT2.generated.cs ├── OneOfT3.generated.cs ├── OneOfT4.generated.cs ├── OneOfT5.generated.cs ├── OneOfT6.generated.cs ├── OneOfT7.generated.cs ├── OneOfT8.generated.cs └── Types │ ├── Assorted.cs │ ├── TrueFalseOrNull.cs │ ├── TrueOrFalse.cs │ ├── YesNoOrMaybe.cs │ └── YesOrNo.cs ├── README.md └── licence.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/debug*.dll 2 | bin/release*.dll 3 | obj 4 | bin/ 5 | deploy 6 | deploy/* 7 | _ReSharper.* 8 | *.csproj.user 9 | *.resharper.user 10 | *.ReSharper.user 11 | *.resharper 12 | *.suo 13 | *.cache 14 | ~$* 15 | *.suo 16 | packages/* 17 | /packages 18 | /*.user 19 | .vs 20 | *.user 21 | project.lock.json 22 | .idea/ 23 | -------------------------------------------------------------------------------- /Generator/Functions.cs_: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace OneOf { 7 | internal static class Functions { 8 | internal static string FormatValue(Type type, T value) => $"{type.FullName}: {value?.ToString()}"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Generator/Generator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Generator/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using static System.IO.Path; 4 | using static System.Reflection.Assembly; 5 | using static System.Linq.Enumerable; 6 | using System; 7 | using System.Collections.Generic; 8 | 9 | var sourceRoot = GetFullPath(Combine(GetDirectoryName(GetExecutingAssembly().Location)!, @"..\..\..\..")); 10 | 11 | for (var i = 1; i < 10; i++) { 12 | var output = GetContent(true, i); 13 | var outpath = Combine(sourceRoot, $"OneOf\\OneOfT{i - 1}.generated.cs"); 14 | File.WriteAllText(outpath, output); 15 | 16 | var output2 = GetContent(false, i); 17 | var outpath2 = Combine(sourceRoot, $"OneOf\\OneOfBaseT{i - 1}.generated.cs"); 18 | File.WriteAllText(outpath2, output2); 19 | } 20 | 21 | for (var i = 10; i < 33; i++) { 22 | var output3 = GetContent(true, i); 23 | var outpath3 = Combine(sourceRoot, $"OneOf.Extended\\OneOfT{i - 1}.generated.cs"); 24 | File.WriteAllText(outpath3, output3); 25 | 26 | var output4 = GetContent(false, i); 27 | var outpath4 = Combine(sourceRoot, $"OneOf.Extended\\OneOfBaseT{i - 1}.generated.cs"); 28 | File.WriteAllText(outpath4, output4); 29 | } 30 | 31 | string GetContent(bool isStruct, int i) { 32 | string RangeJoined(string delimiter, Func selector) => Range(0, i).Joined(delimiter, selector); 33 | string IfStruct(string s, string s2 = "") => isStruct ? s : s2; 34 | 35 | var className = isStruct ? "OneOf" : "OneOfBase"; 36 | var genericArgs = Range(0, i).Select(e => $"T{e}").ToList(); 37 | var genericArg = genericArgs.Joined(", "); 38 | var sb = new StringBuilder(); 39 | 40 | sb.Append(@$"using System; 41 | using static OneOf.Functions; 42 | 43 | namespace OneOf 44 | {{ 45 | public {IfStruct("readonly struct", "class")} {className}<{genericArg}> : IOneOf 46 | {{ 47 | {RangeJoined(@" 48 | ", j => $"readonly T{j} _value{j};")} 49 | readonly int _index; 50 | 51 | {IfStruct( // constructor 52 | $@"OneOf(int index, {RangeJoined(", ", j => $"T{j} value{j} = default")}) 53 | {{ 54 | _index = index; 55 | {RangeJoined(@" 56 | ", j => $"_value{j} = value{j};")} 57 | }}", 58 | $@"protected OneOfBase(OneOf<{genericArg}> input) 59 | {{ 60 | _index = input.Index; 61 | switch (_index) 62 | {{ 63 | {RangeJoined($@" 64 | ", j => $"case {j}: _value{j} = input.AsT{j}; break;")} 65 | default: throw new InvalidOperationException(); 66 | }} 67 | }}" 68 | )} 69 | 70 | public object Value => 71 | _index switch 72 | {{ 73 | {RangeJoined(@" 74 | ", j => $"{j} => _value{j},")} 75 | _ => throw new InvalidOperationException() 76 | }}; 77 | 78 | public int Index => _index; 79 | 80 | {RangeJoined(@" 81 | ", j=> $"public bool IsT{j} => _index == {j};")} 82 | 83 | {RangeJoined(@" 84 | ", j => $@"public T{j} AsT{j} => 85 | _index == {j} ? 86 | _value{j} : 87 | throw new InvalidOperationException($""Cannot return as T{j} as result is T{{_index}}"");")} 88 | 89 | {IfStruct(RangeJoined(@" 90 | ", j => $"public static implicit operator {className}<{genericArg}>(T{j} t) => new {className}<{genericArg}>({j}, value{j}: t);"))} 91 | 92 | public void Switch({RangeJoined(", ", e => $"Action f{e}")}) 93 | {{ 94 | {RangeJoined(@" 95 | ", j => @$"if (_index == {j} && f{j} != null) 96 | {{ 97 | f{j}(_value{j}); 98 | return; 99 | }}")} 100 | throw new InvalidOperationException(); 101 | }} 102 | 103 | public TResult Match({RangeJoined(", ", e => $"Func f{e}")}) 104 | {{ 105 | {RangeJoined(@" 106 | ", j => $@"if (_index == {j} && f{j} != null) 107 | {{ 108 | return f{j}(_value{j}); 109 | }}")} 110 | throw new InvalidOperationException(); 111 | }} 112 | 113 | {IfStruct(genericArgs.Joined(@" 114 | ", bindToType => $@"public static OneOf<{genericArgs.Joined(", ")}> From{bindToType}({bindToType} input) => input;"))} 115 | 116 | {IfStruct(genericArgs.Joined(@" 117 | ", bindToType => { 118 | var resultArgsPrinted = genericArgs.Select(x => { 119 | return x == bindToType ? "TResult" : x; 120 | }).Joined(", "); 121 | return $@" 122 | public OneOf<{resultArgsPrinted}> Map{bindToType}(Func<{bindToType}, TResult> mapFunc) 123 | {{ 124 | if (mapFunc == null) 125 | {{ 126 | throw new ArgumentNullException(nameof(mapFunc)); 127 | }} 128 | return _index switch 129 | {{ 130 | {genericArgs.Joined(@" 131 | ", (x, k) => 132 | x == bindToType ? 133 | $"{k} => mapFunc(As{x})," : 134 | $"{k} => As{x},")} 135 | _ => throw new InvalidOperationException() 136 | }}; 137 | }}"; 138 | }))} 139 | "); 140 | 141 | if (i > 1) { 142 | sb.AppendLine( 143 | RangeJoined(@" 144 | ", j => { 145 | var genericArgWithSkip = Range(0, i).ExceptSingle(j).Joined(", ", e => $"T{e}"); 146 | var remainderType = i == 2 ? genericArgWithSkip : $"OneOf<{genericArgWithSkip}>"; 147 | return $@" 148 | public bool TryPickT{j}(out T{j} value, out {remainderType} remainder) 149 | {{ 150 | value = IsT{j} ? AsT{j} : default; 151 | remainder = _index switch 152 | {{ 153 | {RangeJoined(@" 154 | ", k => 155 | k == j ? 156 | $"{k} => default," : 157 | $"{k} => AsT{k},")} 158 | _ => throw new InvalidOperationException() 159 | }}; 160 | return this.IsT{j}; 161 | }}"; 162 | }) 163 | ); 164 | } 165 | 166 | sb.AppendLine($@" 167 | bool Equals({className}<{genericArg}> other) => 168 | _index == other._index && 169 | _index switch 170 | {{ 171 | {RangeJoined(@" 172 | ", j => @$"{j} => Equals(_value{j}, other._value{j}),")} 173 | _ => false 174 | }}; 175 | 176 | public override bool Equals(object obj) 177 | {{ 178 | if (ReferenceEquals(null, obj)) 179 | {{ 180 | return false; 181 | }} 182 | 183 | {IfStruct( 184 | $"return obj is OneOf<{genericArg}> o && Equals(o);", 185 | $@"if (ReferenceEquals(this, obj)) {{ 186 | return true; 187 | }} 188 | 189 | return obj is OneOfBase<{genericArg}> o && Equals(o);" 190 | )} 191 | }} 192 | 193 | public override string ToString() => 194 | _index switch {{ 195 | {RangeJoined(@" 196 | ", j => $"{j} => FormatValue(_value{j}),")} 197 | _ => throw new InvalidOperationException(""Unexpected index, which indicates a problem in the OneOf codegen."") 198 | }}; 199 | 200 | public override int GetHashCode() 201 | {{ 202 | unchecked 203 | {{ 204 | int hashCode = _index switch 205 | {{ 206 | {RangeJoined(@" 207 | ", j => $"{j} => _value{j}?.GetHashCode(),")} 208 | _ => 0 209 | }} ?? 0; 210 | return (hashCode*397) ^ _index; 211 | }} 212 | }} 213 | }} 214 | }}"); 215 | 216 | return sb.ToString(); 217 | } 218 | 219 | public static class Extensions { 220 | public static string Joined(this IEnumerable source, string delimiter, Func? selector = null) { 221 | if (source == null) { return ""; } 222 | if (selector == null) { return string.Join(delimiter, source); } 223 | return string.Join(delimiter, source.Select(selector)); 224 | } 225 | public static string Joined(this IEnumerable source, string delimiter, Func selector) { 226 | if (source == null) { return ""; } 227 | return string.Join(delimiter, source.Select(selector)); 228 | } 229 | public static IEnumerable ExceptSingle(this IEnumerable source, T single) => source.Except(Repeat(single, 1)); 230 | public static void AppendLineTo(this string? s, StringBuilder sb) => sb.AppendLine(s); 231 | } -------------------------------------------------------------------------------- /Generator/Program.cs._: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Text; 6 | using static System.Linq.Enumerable; 7 | 8 | // the generator exe is in 'bin/$config/$tfm 9 | var sourceRoot = Path.GetFullPath(Path.Combine(Assembly.GetExecutingAssembly().Location, @"..\..\..\..\..")); 10 | 11 | var output = GetContent(true, 1, 10); 12 | var outpath = Path.Combine(sourceRoot, @"OneOf\OneOf.cs"); 13 | File.WriteAllText(outpath, output); 14 | 15 | var output2 = GetContent(false, 1, 10); 16 | var outpath2 = Path.Combine(sourceRoot, @"OneOf\OneOfBase.cs"); 17 | File.WriteAllText(outpath2, output2); 18 | 19 | var output3 = GetContent(true, 10, 33); 20 | var outpath3 = Path.Combine(sourceRoot, @"OneOf.Extended\OneOf.cs"); 21 | File.WriteAllText(outpath3, output3); 22 | 23 | var output4 = GetContent(false, 10, 33); 24 | var outpath4 = Path.Combine(sourceRoot, @"OneOf.Extended\OneOfBase.cs"); 25 | File.WriteAllText(outpath4, output4); 26 | 27 | 28 | string GetContent(bool isStruct, int indexStart, int indexEnd) { 29 | var className = isStruct ? "OneOf" : "OneOfBase"; 30 | var sb = new StringBuilder(); 31 | sb.Append($@"using System; 32 | {(isStruct ? @" 33 | using static OneOf.Functions;" : "")} 34 | 35 | namespace OneOf 36 | {{"); 37 | 38 | for (var i = indexStart; i < indexEnd; i++) { 39 | string RangeJoined(string delimiter, Func selector) => Range(0, i).Joined(delimiter, selector); 40 | string IfStruct(string s, string s2 = "") => isStruct ? s : s2; 41 | string IfNotStruct(string s) => !isStruct ? s : ""; 42 | 43 | var genericArg = RangeJoined(", ", e => $"T{e}"); 44 | 45 | $@" 46 | public {IfStruct("struct", "class")} {className}<{genericArg}> : IOneOf 47 | {{ 48 | {RangeJoined(@" 49 | ", j => $"readonly T{j} _value{j};")} 50 | readonly int _index; 51 | 52 | {IfStruct( 53 | $@"{className}(int index, {RangeJoined(", ", j => $"T{j} value{j} = default")}) 54 | {{ 55 | _index = index; 56 | {RangeJoined(@" 57 | ", j => $"_value{j} = value{j};")} 58 | }}" 59 | )}{IfNotStruct( 60 | $@"protected {className}(OneOf<{genericArg}> input) 61 | {{ 62 | _index = input.Index; 63 | switch (_index) 64 | {{ 65 | {RangeJoined($@" 66 | ", j => $"case {j}: _value{j} = input.AsT{j}; break;")} 67 | default: throw new InvalidOperationException(); 68 | }} 69 | }}" 70 | )} 71 | 72 | public object Value => _index switch 73 | {{ 74 | {RangeJoined(@" 75 | ", k => $"{k} => _value{k},")} 76 | _ => throw new InvalidOperationException() 77 | }}; 78 | 79 | public int Index => _index; 80 | 81 | {RangeJoined(@" 82 | ", j => $@"public bool IsT{j} => _index == {j}; 83 | 84 | public T{j} AsT{j} => 85 | _index == {j} ? 86 | _value{j} : 87 | throw new NotImplementedException($""Cannot return as T{j} as result is T{{_index}}""); 88 | 89 | {IfStruct( 90 | @$"public static implicit operator {className}<{genericArg}>(T{j} t) => new {className}<{genericArg}>({j}, value{j}: t); 91 | " 92 | )}" 93 | )} 94 | 95 | public void Switch({RangeJoined(", ", e => $"Action f{e}")}) 96 | {{ 97 | {RangeJoined(@" 98 | ", j => $@"if (_index == {j} && f{j} != null) 99 | {{ 100 | f{j}(_value{j}); 101 | return; 102 | }}")} 103 | throw new InvalidOperationException(); 104 | }} 105 | 106 | public TResult Match({RangeJoined(", ", e => $"Func f{e}")}) 107 | {{ 108 | {RangeJoined(@" 109 | ", j => $@"if (_index == {j} && f{j} != null) 110 | {{ 111 | return f{j}(_value{j}); 112 | }}")} 113 | throw new InvalidOperationException(); 114 | }}".AppendLineTo(sb); 115 | 116 | if (isStruct) { 117 | var genericArgs = Range(0, i).Select(e => $"T{e}").ToList(); 118 | 119 | $@" 120 | {RangeJoined(@" 121 | ", j => @$"public static OneOf<{genericArgs.Joined(", ")}> FromT{j}(T{j} input) => input;")} 122 | 123 | {genericArgs.Joined(@" 124 | ", bindToType => $@"public OneOf<{genericArgs.Joined(", ", x => x == bindToType ? "TResult" : x)}> Map{bindToType}(Func<{bindToType}, TResult> mapFunc) 125 | {{ 126 | if (mapFunc == null) 127 | {{ 128 | throw new ArgumentNullException(nameof(mapFunc)); 129 | }} 130 | return _index switch 131 | {{ 132 | {RangeJoined(@" 133 | ", k => { 134 | var arg = 135 | bindToType != $"T{k}" ? 136 | $"AsT{k}" : 137 | $"mapFunc(AsT{k})"; 138 | return $"{k} => {arg},"; 139 | })} 140 | _ => throw new InvalidOperationException() 141 | }}; 142 | }}")}".AppendLineTo(sb); 143 | } 144 | 145 | if (i > 1) { 146 | for (var j = 0; j < i; j++) { 147 | 148 | var genericArgWithSkip = Range(0, i).Except(new[] { j }).Joined(", ", e => $"T{e}"); 149 | var remainderType = i == 2 ? genericArgWithSkip : $"OneOf<{genericArgWithSkip}>"; 150 | $@" 151 | public bool TryPickT{j}(out T{j} value, out {remainderType} remainder) 152 | {{ 153 | value = IsT{j} ? AsT{j} : default; 154 | remainder = _index switch 155 | {{ 156 | {RangeJoined(@" 157 | ", k => { 158 | var result = 159 | k == j ? "default" : 160 | i == 2 ? $"AsT{k}" : 161 | k < j ? $"{remainderType}.FromT{k}(AsT{k})" : 162 | $"{remainderType}.FromT{k - 1}(AsT{k})"; 163 | return $"{k} => {result},"; 164 | })} 165 | _ => throw new InvalidOperationException() 166 | }}; 167 | return IsT{j}; 168 | }}".AppendLineTo(sb); 169 | } 170 | } 171 | 172 | $@" 173 | bool Equals({className}<{genericArg}> other) => 174 | _index == other._index && 175 | _index switch 176 | {{ 177 | {RangeJoined("", j => @$" 178 | {j} => Equals(_value{j}, other._value{j}),")} 179 | _ => false 180 | }}; 181 | 182 | {IfStruct( 183 | $"public override bool Equals(object obj) => obj is {className}<{genericArg}> o && Equals(o);", 184 | @$"public override bool Equals(object obj) 185 | {{ 186 | if (ReferenceEquals(null, obj)) 187 | return false; 188 | 189 | if (ReferenceEquals(this, obj)) 190 | return true; 191 | 192 | return obj is {className}<{genericArg}> o && Equals(o); 193 | }}")} 194 | 195 | public override string ToString() 196 | {{{(isStruct ? "" : @" 197 | string FormatValue(Type type, T value) => object.ReferenceEquals(this, value) ? base.ToString() : $""{type.FullName}: {value?.ToString()}"";")} 198 | return _index switch 199 | {{ 200 | {RangeJoined(@" 201 | ", j => $"{j} => FormatValue(typeof(T{j}), _value{j}),")} 202 | _ => throw new InvalidOperationException(""Unexpected index, which indicates a problem in the OneOf codegen."") 203 | }}; 204 | }} 205 | 206 | public override int GetHashCode() 207 | {{ 208 | int hashCode = _index switch 209 | {{ 210 | {RangeJoined(@" 211 | ", j => $"{j} => _value{j}?.GetHashCode() ?? 0,")} 212 | _ => 0 213 | }}; 214 | return (hashCode*397) ^ _index; 215 | }} 216 | }}".AppendLineTo(sb); 217 | } 218 | sb.AppendLine("}"); 219 | return sb.ToString(); 220 | } 221 | 222 | public static class Extensions { 223 | public static string Joined(this IEnumerable source, string delimiter, Func? selector = null) { 224 | if (source == null) { return ""; } 225 | if (selector == null) { return string.Join(delimiter, source); } 226 | return string.Join(delimiter, source.Select(selector)); 227 | } 228 | public static void AppendLineTo(this string? s, StringBuilder sb) => sb.AppendLine(s); 229 | } -------------------------------------------------------------------------------- /OneOf.Extended/Assembly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [assembly: CLSCompliant(true)] 4 | -------------------------------------------------------------------------------- /OneOf.Extended/Functions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OneOf { 4 | internal static class Functions { 5 | internal static string FormatValue(T value) => $"{typeof(T).FullName}: {value?.ToString()}"; 6 | internal static string FormatValue(object @this, object @base, T value) => 7 | ReferenceEquals(@this, value) ? 8 | @base.ToString() : 9 | $"{typeof(T).FullName}: {value?.ToString()}"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /OneOf.Extended/OneOf.Extended.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net35;net451;netstandard1.3 5 | Harry McIntyre 6 | OneOf - Easy Discriminated Unions for c# 7 | Harry McIntyre 8 | 1.0.0 9 | Harry McIntyre 10 | This package extends the OneOf types from OneOf<T0, .., T9> to OneOf<T0, .., T32> for when you really have a lot of options 11 | https://github.com/mcintyre321/OneOf/ 12 | MIT 13 | discriminated unions, return type, match switch 14 | True 15 | OneOf.Extended 16 | Harry McIntyre 17 | OneOf 18 | true 19 | true 20 | snupkg 21 | 22 | 9.0 23 | enable 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /OneOf.FSharp/CreateFile.linq: -------------------------------------------------------------------------------- 1 | 2 | 3 | let getGenericArguments index = [for i in 0 .. index -> sprintf "'t%i%s" i (if i = index then "" else ", ")] |> String.concat "" 4 | 5 | let className index = sprintf "type FsOneOf%i<%s> = %s" (index + 1) (getGenericArguments index) Environment.NewLine 6 | 7 | let getCases index = [for i in 0 .. index -> sprintf " | T%i_%i of 't%i%s" (index + 1) i i Environment.NewLine] |> String.concat "" 8 | 9 | let toOneOfDecl = sprintf " member self.toOneOf =%s match self with%s" Environment.NewLine Environment.NewLine 10 | 11 | let getToOneOfCase oneOfIndex caseIndex = sprintf " | T%i_%i t%i -> OneOf<%s>.op_Implicit t%i%s" (oneOfIndex + 1) caseIndex caseIndex (getGenericArguments oneOfIndex) caseIndex Environment.NewLine 12 | 13 | let getToOneOfCases index = [for i in 0 .. index -> getToOneOfCase index i] |> String.concat "" 14 | 15 | let fromOneOfDecl index = sprintf " static member fromOneOf (oneOf: OneOf<%s>) = %s" (getGenericArguments index) Environment.NewLine 16 | 17 | let getFromOneOfCase index oneOfIndex printComma = sprintf " (fun t%i -> T%i_%i t%i)%s%s" index (oneOfIndex + 1) index index (if printComma then "," else "") Environment.NewLine 18 | 19 | let getFromOneOfCases index = sprintf " oneOf.Match(%s%s )" Environment.NewLine ([for i in 0 .. index -> getFromOneOfCase i index (i <> index)] |> String.concat "") 20 | 21 | let getFromOneOf index = sprintf "%s%s" (fromOneOfDecl index) (getFromOneOfCases index) 22 | 23 | let getToOneOf index = sprintf "%s%s" toOneOfDecl (getToOneOfCases index) 24 | 25 | let getType index = 26 | let typeDeclaration = className index 27 | let cases = getCases index 28 | let toOneOf = getToOneOf index 29 | let fromOneOf = getFromOneOf index 30 | sprintf "%s%s%s%s%s%s" typeDeclaration cases toOneOf fromOneOf Environment.NewLine Environment.NewLine 31 | 32 | let getContent = 33 | let fileIntro = @"module OneOf.FSharp 34 | 35 | open OneOf 36 | " 37 | let results = List.Cons(fileIntro, [for i in 0 .. 8 -> getType i]) 38 | String.concat "" results 39 | 40 | let main = 41 | let output = getContent 42 | let outpath = Path.Combine(Path.GetDirectoryName(Util.CurrentQueryPath), "FsOneOf.fs"); 43 | File.WriteAllText(outpath, output); 44 | 0 -------------------------------------------------------------------------------- /OneOf.FSharp/FsOneOf.fs: -------------------------------------------------------------------------------- 1 | module OneOf.FSharp 2 | 3 | open OneOf 4 | type FsOneOf1<'t0> = 5 | | T1_0 of 't0 6 | member self.toOneOf = 7 | match self with 8 | | T1_0 t0 -> OneOf<'t0>.op_Implicit t0 9 | static member fromOneOf (oneOf: OneOf<'t0>) = 10 | oneOf.Match( 11 | (fun t0 -> T1_0 t0) 12 | ) 13 | 14 | type FsOneOf2<'t0, 't1> = 15 | | T2_0 of 't0 16 | | T2_1 of 't1 17 | member self.toOneOf = 18 | match self with 19 | | T2_0 t0 -> OneOf<'t0, 't1>.op_Implicit t0 20 | | T2_1 t1 -> OneOf<'t0, 't1>.op_Implicit t1 21 | static member fromOneOf (oneOf: OneOf<'t0, 't1>) = 22 | oneOf.Match( 23 | (fun t0 -> T2_0 t0), 24 | (fun t1 -> T2_1 t1) 25 | ) 26 | 27 | type FsOneOf3<'t0, 't1, 't2> = 28 | | T3_0 of 't0 29 | | T3_1 of 't1 30 | | T3_2 of 't2 31 | member self.toOneOf = 32 | match self with 33 | | T3_0 t0 -> OneOf<'t0, 't1, 't2>.op_Implicit t0 34 | | T3_1 t1 -> OneOf<'t0, 't1, 't2>.op_Implicit t1 35 | | T3_2 t2 -> OneOf<'t0, 't1, 't2>.op_Implicit t2 36 | static member fromOneOf (oneOf: OneOf<'t0, 't1, 't2>) = 37 | oneOf.Match( 38 | (fun t0 -> T3_0 t0), 39 | (fun t1 -> T3_1 t1), 40 | (fun t2 -> T3_2 t2) 41 | ) 42 | 43 | type FsOneOf4<'t0, 't1, 't2, 't3> = 44 | | T4_0 of 't0 45 | | T4_1 of 't1 46 | | T4_2 of 't2 47 | | T4_3 of 't3 48 | member self.toOneOf = 49 | match self with 50 | | T4_0 t0 -> OneOf<'t0, 't1, 't2, 't3>.op_Implicit t0 51 | | T4_1 t1 -> OneOf<'t0, 't1, 't2, 't3>.op_Implicit t1 52 | | T4_2 t2 -> OneOf<'t0, 't1, 't2, 't3>.op_Implicit t2 53 | | T4_3 t3 -> OneOf<'t0, 't1, 't2, 't3>.op_Implicit t3 54 | static member fromOneOf (oneOf: OneOf<'t0, 't1, 't2, 't3>) = 55 | oneOf.Match( 56 | (fun t0 -> T4_0 t0), 57 | (fun t1 -> T4_1 t1), 58 | (fun t2 -> T4_2 t2), 59 | (fun t3 -> T4_3 t3) 60 | ) 61 | 62 | type FsOneOf5<'t0, 't1, 't2, 't3, 't4> = 63 | | T5_0 of 't0 64 | | T5_1 of 't1 65 | | T5_2 of 't2 66 | | T5_3 of 't3 67 | | T5_4 of 't4 68 | member self.toOneOf = 69 | match self with 70 | | T5_0 t0 -> OneOf<'t0, 't1, 't2, 't3, 't4>.op_Implicit t0 71 | | T5_1 t1 -> OneOf<'t0, 't1, 't2, 't3, 't4>.op_Implicit t1 72 | | T5_2 t2 -> OneOf<'t0, 't1, 't2, 't3, 't4>.op_Implicit t2 73 | | T5_3 t3 -> OneOf<'t0, 't1, 't2, 't3, 't4>.op_Implicit t3 74 | | T5_4 t4 -> OneOf<'t0, 't1, 't2, 't3, 't4>.op_Implicit t4 75 | static member fromOneOf (oneOf: OneOf<'t0, 't1, 't2, 't3, 't4>) = 76 | oneOf.Match( 77 | (fun t0 -> T5_0 t0), 78 | (fun t1 -> T5_1 t1), 79 | (fun t2 -> T5_2 t2), 80 | (fun t3 -> T5_3 t3), 81 | (fun t4 -> T5_4 t4) 82 | ) 83 | 84 | type FsOneOf6<'t0, 't1, 't2, 't3, 't4, 't5> = 85 | | T6_0 of 't0 86 | | T6_1 of 't1 87 | | T6_2 of 't2 88 | | T6_3 of 't3 89 | | T6_4 of 't4 90 | | T6_5 of 't5 91 | member self.toOneOf = 92 | match self with 93 | | T6_0 t0 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5>.op_Implicit t0 94 | | T6_1 t1 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5>.op_Implicit t1 95 | | T6_2 t2 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5>.op_Implicit t2 96 | | T6_3 t3 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5>.op_Implicit t3 97 | | T6_4 t4 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5>.op_Implicit t4 98 | | T6_5 t5 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5>.op_Implicit t5 99 | static member fromOneOf (oneOf: OneOf<'t0, 't1, 't2, 't3, 't4, 't5>) = 100 | oneOf.Match( 101 | (fun t0 -> T6_0 t0), 102 | (fun t1 -> T6_1 t1), 103 | (fun t2 -> T6_2 t2), 104 | (fun t3 -> T6_3 t3), 105 | (fun t4 -> T6_4 t4), 106 | (fun t5 -> T6_5 t5) 107 | ) 108 | 109 | type FsOneOf7<'t0, 't1, 't2, 't3, 't4, 't5, 't6> = 110 | | T7_0 of 't0 111 | | T7_1 of 't1 112 | | T7_2 of 't2 113 | | T7_3 of 't3 114 | | T7_4 of 't4 115 | | T7_5 of 't5 116 | | T7_6 of 't6 117 | member self.toOneOf = 118 | match self with 119 | | T7_0 t0 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6>.op_Implicit t0 120 | | T7_1 t1 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6>.op_Implicit t1 121 | | T7_2 t2 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6>.op_Implicit t2 122 | | T7_3 t3 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6>.op_Implicit t3 123 | | T7_4 t4 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6>.op_Implicit t4 124 | | T7_5 t5 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6>.op_Implicit t5 125 | | T7_6 t6 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6>.op_Implicit t6 126 | static member fromOneOf (oneOf: OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6>) = 127 | oneOf.Match( 128 | (fun t0 -> T7_0 t0), 129 | (fun t1 -> T7_1 t1), 130 | (fun t2 -> T7_2 t2), 131 | (fun t3 -> T7_3 t3), 132 | (fun t4 -> T7_4 t4), 133 | (fun t5 -> T7_5 t5), 134 | (fun t6 -> T7_6 t6) 135 | ) 136 | 137 | type FsOneOf8<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> = 138 | | T8_0 of 't0 139 | | T8_1 of 't1 140 | | T8_2 of 't2 141 | | T8_3 of 't3 142 | | T8_4 of 't4 143 | | T8_5 of 't5 144 | | T8_6 of 't6 145 | | T8_7 of 't7 146 | member self.toOneOf = 147 | match self with 148 | | T8_0 t0 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7>.op_Implicit t0 149 | | T8_1 t1 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7>.op_Implicit t1 150 | | T8_2 t2 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7>.op_Implicit t2 151 | | T8_3 t3 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7>.op_Implicit t3 152 | | T8_4 t4 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7>.op_Implicit t4 153 | | T8_5 t5 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7>.op_Implicit t5 154 | | T8_6 t6 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7>.op_Implicit t6 155 | | T8_7 t7 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7>.op_Implicit t7 156 | static member fromOneOf (oneOf: OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7>) = 157 | oneOf.Match( 158 | (fun t0 -> T8_0 t0), 159 | (fun t1 -> T8_1 t1), 160 | (fun t2 -> T8_2 t2), 161 | (fun t3 -> T8_3 t3), 162 | (fun t4 -> T8_4 t4), 163 | (fun t5 -> T8_5 t5), 164 | (fun t6 -> T8_6 t6), 165 | (fun t7 -> T8_7 t7) 166 | ) 167 | 168 | type FsOneOf9<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8> = 169 | | T9_0 of 't0 170 | | T9_1 of 't1 171 | | T9_2 of 't2 172 | | T9_3 of 't3 173 | | T9_4 of 't4 174 | | T9_5 of 't5 175 | | T9_6 of 't6 176 | | T9_7 of 't7 177 | | T9_8 of 't8 178 | member self.toOneOf = 179 | match self with 180 | | T9_0 t0 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>.op_Implicit t0 181 | | T9_1 t1 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>.op_Implicit t1 182 | | T9_2 t2 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>.op_Implicit t2 183 | | T9_3 t3 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>.op_Implicit t3 184 | | T9_4 t4 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>.op_Implicit t4 185 | | T9_5 t5 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>.op_Implicit t5 186 | | T9_6 t6 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>.op_Implicit t6 187 | | T9_7 t7 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>.op_Implicit t7 188 | | T9_8 t8 -> OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>.op_Implicit t8 189 | static member fromOneOf (oneOf: OneOf<'t0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8>) = 190 | oneOf.Match( 191 | (fun t0 -> T9_0 t0), 192 | (fun t1 -> T9_1 t1), 193 | (fun t2 -> T9_2 t2), 194 | (fun t3 -> T9_3 t3), 195 | (fun t4 -> T9_4 t4), 196 | (fun t5 -> T9_5 t5), 197 | (fun t6 -> T9_6 t6), 198 | (fun t7 -> T9_7 t7), 199 | (fun t8 -> T9_8 t8) 200 | ) 201 | 202 | -------------------------------------------------------------------------------- /OneOf.FSharp/OneOf.FSharp.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OneOf.FSharp 5 | netstandard2.0 6 | portable 7 | Harry McIntyre 8 | OneOf - Easy Discriminated Unions for c# 9 | Harry McIntyre 10 | 1.0.0 11 | Harry McIntyre 12 | This is an FSharp library for interop with the C# OneOf Types 13 | https://github.com/mcintyre321/OneOf/ 14 | MIT 15 | discriminated unions, return type, match switch 16 | True 17 | OneOf.FSharp 18 | Harry McIntyre 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /OneOf.SourceGenerator.AnalyzerTests/AnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Xunit; 8 | 9 | namespace OneOf.SourceGenerator.AnalyzerTests 10 | { 11 | public class AnalyzerTests 12 | { 13 | [Fact] 14 | public void GenerateOneOfAttribute() 15 | { 16 | const string expectedCode = @"// 17 | using System; 18 | 19 | #pragma warning disable 1591 20 | 21 | namespace OneOf 22 | { 23 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] 24 | internal sealed class GenerateOneOfAttribute : Attribute 25 | { 26 | } 27 | } 28 | "; 29 | AssertCorrectSourceCodeIsGeneratedWithNoDiagnostics(string.Empty, expectedCode, "GenerateOneOfAttribute.g.cs", 2); 30 | } 31 | 32 | [Fact] 33 | public void GeneratesPublicClass() 34 | { 35 | const string input = @" 36 | using OneOf; 37 | 38 | namespace Foo 39 | { 40 | [GenerateOneOf] 41 | public partial class StringOrNumber : OneOfBase { } 42 | } 43 | "; 44 | 45 | const string expectedCode = @"// 46 | #pragma warning disable 1591 47 | 48 | namespace Foo 49 | { 50 | partial class StringOrNumber 51 | { 52 | public StringOrNumber(OneOf.OneOf _) : base(_) { } 53 | 54 | public static implicit operator StringOrNumber(string _) => new StringOrNumber(_); 55 | public static explicit operator string(StringOrNumber _) => _.AsT0; 56 | 57 | public static implicit operator StringOrNumber(int _) => new StringOrNumber(_); 58 | public static explicit operator int(StringOrNumber _) => _.AsT1; 59 | 60 | public static implicit operator StringOrNumber(uint _) => new StringOrNumber(_); 61 | public static explicit operator uint(StringOrNumber _) => _.AsT2; 62 | } 63 | }"; 64 | 65 | AssertCorrectSourceCodeIsGeneratedWithNoDiagnostics(input, expectedCode, "Foo_StringOrNumber.g.cs"); 66 | } 67 | 68 | [Fact] 69 | public void GeneratesInternalClass() 70 | { 71 | const string input = @" 72 | using OneOf; 73 | 74 | namespace Foo 75 | { 76 | [GenerateOneOf] 77 | internal partial class StringOrNumber : OneOfBase { } 78 | } 79 | "; 80 | 81 | const string expectedCode = @"// 82 | #pragma warning disable 1591 83 | 84 | namespace Foo 85 | { 86 | partial class StringOrNumber 87 | { 88 | public StringOrNumber(OneOf.OneOf _) : base(_) { } 89 | 90 | public static implicit operator StringOrNumber(string _) => new StringOrNumber(_); 91 | public static explicit operator string(StringOrNumber _) => _.AsT0; 92 | 93 | public static implicit operator StringOrNumber(int _) => new StringOrNumber(_); 94 | public static explicit operator int(StringOrNumber _) => _.AsT1; 95 | 96 | public static implicit operator StringOrNumber(uint _) => new StringOrNumber(_); 97 | public static explicit operator uint(StringOrNumber _) => _.AsT2; 98 | } 99 | }"; 100 | 101 | AssertCorrectSourceCodeIsGeneratedWithNoDiagnostics(input, expectedCode, "Foo_StringOrNumber.g.cs"); 102 | } 103 | 104 | [Fact] 105 | public void GeneratesInternalClassWithoutExplicitVisibility() 106 | { 107 | const string input = @" 108 | using OneOf; 109 | 110 | namespace Foo 111 | { 112 | [GenerateOneOf] 113 | partial class StringOrNumber : OneOfBase { } 114 | } 115 | "; 116 | 117 | const string expectedCode = @"// 118 | #pragma warning disable 1591 119 | 120 | namespace Foo 121 | { 122 | partial class StringOrNumber 123 | { 124 | public StringOrNumber(OneOf.OneOf _) : base(_) { } 125 | 126 | public static implicit operator StringOrNumber(string _) => new StringOrNumber(_); 127 | public static explicit operator string(StringOrNumber _) => _.AsT0; 128 | 129 | public static implicit operator StringOrNumber(int _) => new StringOrNumber(_); 130 | public static explicit operator int(StringOrNumber _) => _.AsT1; 131 | 132 | public static implicit operator StringOrNumber(uint _) => new StringOrNumber(_); 133 | public static explicit operator uint(StringOrNumber _) => _.AsT2; 134 | } 135 | }"; 136 | 137 | AssertCorrectSourceCodeIsGeneratedWithNoDiagnostics(input, expectedCode, "Foo_StringOrNumber.g.cs"); 138 | } 139 | 140 | [Fact] 141 | public void Class_Must_Be_Top_Level() 142 | { 143 | const string input = @" 144 | using OneOf; 145 | 146 | namespace Foo 147 | { 148 | public static class A 149 | { 150 | [GenerateOneOf] 151 | public partial class StringOrNumber : OneOfBase { } 152 | } 153 | } 154 | "; 155 | 156 | AssertDiagnosticErrorIsReturned(input, GeneratorDiagnosticDescriptors.TopLevelError.Id); 157 | } 158 | 159 | [Fact] 160 | public void Cannot_Use_Generator_With_Object_type() 161 | { 162 | const string input = @" 163 | using OneOf; 164 | 165 | namespace Foo 166 | { 167 | [GenerateOneOf] 168 | public partial class ObjectOrNumber : OneOfBase { } 169 | } 170 | "; 171 | AssertDiagnosticErrorIsReturned(input, GeneratorDiagnosticDescriptors.ObjectIsOneOfType.Id); 172 | } 173 | 174 | [Fact] 175 | public void Class_Must_Be_Derived_From_OneOfBase() 176 | { 177 | const string input = @" 178 | using OneOf; 179 | 180 | namespace Foo 181 | { 182 | [GenerateOneOf] 183 | public partial class ObjectOrNumber : MyClass { } 184 | 185 | public class MyClass 186 | { 187 | } 188 | } 189 | "; 190 | AssertDiagnosticErrorIsReturned(input, GeneratorDiagnosticDescriptors.WrongBaseType.Id); 191 | } 192 | 193 | [Fact] 194 | public void User_Defined_Conversions_To_Or_From_An_Interface_Are_Not_Allowed() 195 | { 196 | const string input = @" 197 | using OneOf; 198 | 199 | namespace Foo 200 | { 201 | [GenerateOneOf] 202 | public partial class ObjectOrNumber : OneOfBase { } 203 | 204 | public interface IFoo 205 | { 206 | } 207 | } 208 | "; 209 | AssertDiagnosticErrorIsReturned(input, GeneratorDiagnosticDescriptors.UserDefinedConversionsToOrFromAnInterfaceAreNotAllowed.Id); 210 | } 211 | 212 | [Fact] 213 | public void GeneratesClassWithConflictingNamespaces() 214 | { 215 | const string input = $@" 216 | using OneOf; 217 | using Bar; 218 | using Foo.Bar; 219 | 220 | namespace Foo.Bar 221 | {{ 222 | public class Class1 223 | {{ 224 | }} 225 | }} 226 | 227 | namespace Bar 228 | {{ 229 | public class Class2 230 | {{ 231 | }} 232 | }} 233 | 234 | namespace Foo 235 | {{ 236 | [GenerateOneOf] 237 | partial class FooBar : OneOfBase {{ }} 238 | }} 239 | "; 240 | 241 | const string expectedCode = @"// 242 | #pragma warning disable 1591 243 | 244 | namespace Foo 245 | { 246 | partial class FooBar 247 | { 248 | public FooBar(OneOf.OneOf _) : base(_) { } 249 | 250 | public static implicit operator FooBar(global::Foo.Bar.Class1 _) => new FooBar(_); 251 | public static explicit operator global::Foo.Bar.Class1(FooBar _) => _.AsT0; 252 | 253 | public static implicit operator FooBar(global::Bar.Class2 _) => new FooBar(_); 254 | public static explicit operator global::Bar.Class2(FooBar _) => _.AsT1; 255 | } 256 | }"; 257 | 258 | AssertCorrectSourceCodeIsGeneratedWithNoDiagnostics(input, expectedCode, "Foo_FooBar.g.cs"); 259 | } 260 | 261 | private static void AssertCorrectSourceCodeIsGeneratedWithNoDiagnostics(string inputSource, string expectedCode, string generatedFileName, int expectedCompilationFileCount = 3) 262 | { 263 | var parsedAttribute = CSharpSyntaxTree.ParseText(expectedCode); 264 | 265 | var inputCompilation = CreateCompilation(inputSource); 266 | 267 | GeneratorDriver driver = CSharpGeneratorDriver.Create(new OneOfGenerator()); 268 | 269 | driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); 270 | 271 | Assert.True(diagnostics.IsEmpty); 272 | 273 | Assert.Equal(expectedCompilationFileCount, outputCompilation.SyntaxTrees.Count()); 274 | 275 | Assert.Empty(outputCompilation.GetDiagnostics()); 276 | 277 | var compiledAttribute = outputCompilation.SyntaxTrees.Single(e => e.FilePath.Contains(generatedFileName)); 278 | 279 | Assert.True(parsedAttribute.IsEquivalentTo(compiledAttribute)); 280 | 281 | Assert.True(outputCompilation.GetDiagnostics().IsEmpty); 282 | } 283 | 284 | private static void AssertDiagnosticErrorIsReturned(string inputSource, string diagnosticId) 285 | { 286 | var inputCompilation = CreateCompilation(inputSource); 287 | 288 | GeneratorDriver driver = CSharpGeneratorDriver.Create(new OneOfGenerator()); 289 | 290 | driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out _, out var diagnostics); 291 | 292 | Assert.Contains(diagnostics, d => d.Id == diagnosticId && d.Severity == DiagnosticSeverity.Error); 293 | } 294 | 295 | private static Compilation CreateCompilation(string source) 296 | { 297 | var references = new List 298 | { 299 | MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location), 300 | MetadataReference.CreateFromFile(typeof(OneOfBase<>).GetTypeInfo().Assembly.Location), 301 | MetadataReference.CreateFromFile(AppDomain.CurrentDomain.GetAssemblies().Single(a => a.GetName().Name == "netstandard").Location) 302 | }; 303 | 304 | //https://github.com/dotnet/roslyn/issues/49498#issuecomment-734452762 305 | foreach (var assembly in Assembly.GetEntryAssembly()!.GetReferencedAssemblies()) 306 | { 307 | references.Add(MetadataReference.CreateFromFile(Assembly.Load(assembly).Location)); 308 | } 309 | 310 | return CSharpCompilation.Create("compilation", new[] { CSharpSyntaxTree.ParseText(source) }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); 311 | } 312 | } 313 | } -------------------------------------------------------------------------------- /OneOf.SourceGenerator.AnalyzerTests/OneOf.SourceGenerator.AnalyzerTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /OneOf.SourceGenerator.Tests/OneOf.SourceGenerator.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /OneOf.SourceGenerator.Tests/SourceGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xunit; 3 | 4 | namespace OneOf.SourceGenerator.Tests 5 | { 6 | public class SourceGeneratorTests 7 | { 8 | [Fact] 9 | public void GenerateOneOf_Generates_Correct_Classes_For_Struct_Types() 10 | { 11 | const string testName = "test"; 12 | const int testNumber = 3; 13 | 14 | StringOrNumber stringOrNumber = testName; 15 | StringOrNumber stringOrNumberToCompare = testName; 16 | 17 | Assert.Equal(testName, stringOrNumber); 18 | Assert.Equal(stringOrNumberToCompare, stringOrNumber); 19 | 20 | stringOrNumber = testNumber; 21 | stringOrNumberToCompare = testNumber; 22 | 23 | Assert.Equal(testNumber, (int)stringOrNumber); 24 | Assert.True(testNumber == (int)stringOrNumber); 25 | Assert.Equal(stringOrNumberToCompare, stringOrNumber); 26 | } 27 | 28 | [Fact] 29 | public void GenerateOneOf_Can_Assign_To_Struct_Type() 30 | { 31 | const string testName = "test"; 32 | 33 | StringOrNumber stringOrNumber = testName; 34 | 35 | string name = (string)stringOrNumber; 36 | 37 | Assert.Equal(testName, name); 38 | } 39 | 40 | [Fact] 41 | public void GenerateOneOf_Generates_Correct_Classes_For_Reference_Types() 42 | { 43 | MyClass testClass = new(); 44 | MyClass2 testClass2 = new(); 45 | 46 | MyClass2OrMyClass myClass2OrMyClass = testClass; 47 | MyClass2OrMyClass myClass2OrMyClassToCompare = testClass; 48 | 49 | Assert.Equal(testClass, (MyClass)myClass2OrMyClass); 50 | Assert.Equal((MyClass)myClass2OrMyClass, (MyClass)myClass2OrMyClassToCompare); 51 | 52 | myClass2OrMyClass = testClass2; 53 | myClass2OrMyClassToCompare = testClass2; 54 | 55 | Assert.Equal(testClass2, (MyClass2)myClass2OrMyClass); 56 | Assert.Equal((MyClass2)myClass2OrMyClass, (MyClass2)myClass2OrMyClassToCompare); 57 | } 58 | 59 | [Fact] 60 | public void GenerateOneOf_Can_Assign_To_Reference_Type() 61 | { 62 | MyClass testClass = new(); 63 | 64 | MyClass2OrMyClass myClass2OrMyClass = testClass; 65 | 66 | var test = (MyClass)myClass2OrMyClass; 67 | 68 | Assert.Equal(testClass, test); 69 | } 70 | 71 | [Fact] 72 | public void GenerateOneOf_Works_With_Class_Names_Conflicts() 73 | { 74 | NotOneOf.OneOf notOneOf = new(); 75 | 76 | MyClassOrFakeOneOf myClass2OrFakeOneOf = notOneOf; 77 | 78 | var test = (NotOneOf.OneOf)myClass2OrFakeOneOf; 79 | 80 | Assert.Equal(notOneOf, test); 81 | } 82 | 83 | [Fact] 84 | public void GenerateOneOf_Works_With_Simple_Generic_Types() 85 | { 86 | SimpleGeneric simple1 = new List { "a", "b", "c" }; 87 | Assert.True(simple1.IsT0); 88 | 89 | SimpleGeneric simple2 = new List { 1, 2, 3 }; 90 | Assert.True(simple2.IsT1); 91 | 92 | SimpleGeneric simple3 = new Dictionary { { 1, "a" }, { 2, "b" }, { 3, "c" } }; 93 | Assert.True(simple3.IsT2); 94 | } 95 | 96 | [Fact] 97 | public void GenerateOneOf_Works_With_Nested_Generics() 98 | { 99 | NestedGeneric nested1 = new List> { new() { "a", "b", "c" } }; 100 | Assert.True(nested1.IsT0); 101 | 102 | NestedGeneric nested2 = new Dictionary, string> { { new List { "a", "b", "c" }, "d" } }; 103 | Assert.True(nested2.IsT2); 104 | } 105 | 106 | [Fact] 107 | public void GenerateOneOf_Works_With_Open_Generics_With_Records() 108 | { 109 | OpenGenericWithRecords open = new Ok(new MyClass()); 110 | Assert.True(open.IsT0); 111 | 112 | OpenGenericWithRecords open2 = new Error(new MyClass2()); 113 | Assert.True(open2.IsT1); 114 | } 115 | 116 | [Fact] 117 | public void GenerateOneOf_Works_With_Open_Generics_And_Nested_Generics() 118 | { 119 | OpenGenericWithRecords, MyClass2> open = new Ok>(new List { 1, 2, 3 }); 120 | Assert.True(open.IsT0); 121 | } 122 | 123 | [Fact] 124 | public void GenerateOneOf_Works_With_Open_And_Closed_Generics() 125 | { 126 | OpenGenericWithClosed openWithClosed = new Ok(new MyClass()); 127 | Assert.True(openWithClosed.IsT0); 128 | 129 | OpenGenericWithClosed openWithClosed2 = new MyClass(); 130 | Assert.True(openWithClosed2.IsT1); 131 | } 132 | } 133 | 134 | [GenerateOneOf] 135 | public partial class NestedGeneric : OneOfBase>, List, Dictionary, string>> { } 136 | 137 | [GenerateOneOf] 138 | public partial class SimpleGeneric : OneOfBase, List, Dictionary> { } 139 | 140 | [GenerateOneOf] 141 | internal partial class StringOrNumber : OneOfBase { } 142 | 143 | [GenerateOneOf] 144 | public partial class MyClass2OrMyClass : OneOfBase { } 145 | 146 | [GenerateOneOf] 147 | public partial class MyClassOrFakeOneOf : OneOfBase { } 148 | 149 | public class MyClass 150 | { 151 | 152 | } 153 | 154 | public class MyClass2 155 | { 156 | 157 | } 158 | 159 | public record Error 160 | ( 161 | TError ErrorData 162 | ); 163 | 164 | public record Ok 165 | ( 166 | TResult Data 167 | ); 168 | 169 | [GenerateOneOf] 170 | public partial class OpenGenericWithRecords : OneOfBase, Error> 171 | { 172 | } 173 | 174 | [GenerateOneOf] 175 | public partial class OpenGenericWithClosed : OneOfBase, MyClass> 176 | { 177 | } 178 | } 179 | 180 | namespace NotOneOf 181 | { 182 | public class OneOf 183 | { 184 | 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /OneOf.SourceGenerator/GeneratorDiagnosticDescriptors.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace OneOf 4 | { 5 | public class GeneratorDiagnosticDescriptors 6 | { 7 | public static readonly DiagnosticDescriptor TopLevelError = new(id: "ONEOFGEN001", 8 | title: "Class must be top level", 9 | messageFormat: "Class '{0}' using OneOfGenerator must be top level", 10 | category: "OneOfGenerator", 11 | DiagnosticSeverity.Error, 12 | isEnabledByDefault: true); 13 | 14 | public static readonly DiagnosticDescriptor WrongBaseType = new(id: "ONEOFGEN002", 15 | title: "Class must inherit from OneOfBase", 16 | messageFormat: "Class '{0}' does not inherit from OneOfBase", 17 | category: "OneOfGenerator", 18 | DiagnosticSeverity.Error, 19 | isEnabledByDefault: true); 20 | 21 | public static readonly DiagnosticDescriptor ObjectIsOneOfType = new(id: "ONEOFGEN004", 22 | title: "Object is not a valid type parameter", 23 | messageFormat: "Defined conversions to or from a base type are not allowed for class '{0}'", 24 | category: "OneOfGenerator", 25 | DiagnosticSeverity.Error, 26 | isEnabledByDefault: true); 27 | 28 | public static readonly DiagnosticDescriptor UserDefinedConversionsToOrFromAnInterfaceAreNotAllowed = new(id: "ONEOFGEN005", 29 | title: "user-defined conversions to or from an interface are not allowed", 30 | messageFormat: "user-defined conversions to or from an interface are not allowed", 31 | category: "OneOfGenerator", 32 | DiagnosticSeverity.Error, 33 | isEnabledByDefault: true); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /OneOf.SourceGenerator/OneOf.SourceGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Harry McIntyre, Damian Romanowski 6 | OneOf - Easy Discriminated Unions for c# 7 | Harry McIntyre 8 | 1.0.0 9 | Harry McIntyre 10 | This source generator automaticly implements OneOfBase hierarchies 11 | https://github.com/mcintyre321/OneOf/ 12 | MIT 13 | discriminated unions, return type, match switch, generator, source generator 14 | True 15 | OneOf.SourceGenerator 16 | Harry McIntyre 17 | OneOf 18 | true 19 | preview 20 | enable 21 | true 22 | false 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | all 37 | runtime; build; native; contentfiles; analyzers; buildtransitive 38 | 39 | 40 | all 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /OneOf.SourceGenerator/OneOfGenerator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using System; 5 | using System.Collections.Immutable; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace OneOf.SourceGenerator 10 | { 11 | [Generator] 12 | public class OneOfGenerator : IIncrementalGenerator 13 | { 14 | private const string AttributeName = "GenerateOneOfAttribute"; 15 | private const string AttributeNamespace = "OneOf"; 16 | 17 | private readonly string _attributeText = $@"// 18 | using System; 19 | 20 | #pragma warning disable 1591 21 | 22 | namespace {AttributeNamespace} 23 | {{ 24 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] 25 | internal sealed class {AttributeName} : Attribute 26 | {{ 27 | }} 28 | }} 29 | "; 30 | 31 | public void Initialize(IncrementalGeneratorInitializationContext context) 32 | { 33 | context.RegisterPostInitializationOutput(ctx => ctx.AddSource($"{AttributeName}.g.cs", _attributeText)); 34 | 35 | var oneOfClasses = context.SyntaxProvider 36 | .ForAttributeWithMetadataName( 37 | fullyQualifiedMetadataName: $"{AttributeNamespace}.{AttributeName}", 38 | predicate: static (s, _) => IsSyntaxTargetForGeneration(s), 39 | transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)) 40 | .Where(static m => m is not null) 41 | .Collect(); 42 | 43 | context.RegisterSourceOutput(oneOfClasses, Execute); 44 | 45 | 46 | static bool IsSyntaxTargetForGeneration(SyntaxNode node) 47 | { 48 | return node is ClassDeclarationSyntax classDeclarationSyntax 49 | && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); 50 | } 51 | 52 | static INamedTypeSymbol? GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context) 53 | { 54 | var symbol = context.TargetSymbol; 55 | 56 | if (symbol is not INamedTypeSymbol namedTypeSymbol) 57 | { 58 | return null; 59 | } 60 | 61 | var attributeData = namedTypeSymbol.GetAttributes().FirstOrDefault(ad => 62 | string.Equals(ad.AttributeClass?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), $"global::{AttributeNamespace}.{AttributeName}")); 63 | 64 | return attributeData is null ? null : namedTypeSymbol; 65 | } 66 | } 67 | 68 | private static string GenerateClassSource(INamedTypeSymbol classSymbol, 69 | ImmutableArray typeParameters, ImmutableArray typeArguments) 70 | { 71 | var paramArgPairs = 72 | typeParameters.Zip(typeArguments, (param, arg) => (param, arg)); 73 | 74 | var oneOfGenericPart = GetGenericPart(typeArguments); 75 | 76 | var classNameWithGenericTypes = $"{classSymbol.Name}{GetOpenGenericPart(classSymbol)}"; 77 | 78 | StringBuilder source = new($@"// 79 | #pragma warning disable 1591 80 | 81 | namespace {classSymbol.ContainingNamespace.ToDisplayString()} 82 | {{ 83 | partial class {classNameWithGenericTypes}"); 84 | 85 | source.Append($@" 86 | {{ 87 | public {classSymbol.Name}(OneOf.OneOf<{oneOfGenericPart}> _) : base(_) {{ }} 88 | "); 89 | 90 | foreach (var (param, arg) in paramArgPairs) 91 | { 92 | source.Append($@" 93 | public static implicit operator {classNameWithGenericTypes}({arg.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} _) => new {classNameWithGenericTypes}(_); 94 | public static explicit operator {arg.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}({classNameWithGenericTypes} _) => _.As{param.Name}; 95 | "); 96 | } 97 | 98 | source.Append(@" } 99 | }"); 100 | return source.ToString(); 101 | } 102 | 103 | private static void Execute(SourceProductionContext context, ImmutableArray symbols) 104 | { 105 | foreach (var namedTypeSymbol in symbols.Where(symbol => symbol is not null)) 106 | { 107 | var classSource = ProcessClass(namedTypeSymbol!, context); 108 | 109 | if (classSource is null) 110 | { 111 | continue; 112 | } 113 | 114 | context.AddSource($"{namedTypeSymbol!.ContainingNamespace}_{namedTypeSymbol.Name}.g.cs", classSource); 115 | } 116 | } 117 | 118 | private static string? ProcessClass(INamedTypeSymbol classSymbol, SourceProductionContext context) 119 | { 120 | var attributeLocation = classSymbol.Locations.FirstOrDefault() ?? Location.None; 121 | 122 | if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) 123 | { 124 | CreateDiagnosticError(GeneratorDiagnosticDescriptors.TopLevelError); 125 | return null; 126 | } 127 | 128 | if (classSymbol.BaseType is null || classSymbol.BaseType.Name != "OneOfBase" || classSymbol.BaseType.ContainingNamespace.ToString() != "OneOf") 129 | { 130 | CreateDiagnosticError(GeneratorDiagnosticDescriptors.WrongBaseType); 131 | return null; 132 | } 133 | 134 | var typeArguments = classSymbol.BaseType.TypeArguments; 135 | 136 | foreach (var typeSymbol in typeArguments) 137 | { 138 | if (typeSymbol.Name == nameof(Object)) 139 | { 140 | CreateDiagnosticError(GeneratorDiagnosticDescriptors.ObjectIsOneOfType); 141 | return null; 142 | } 143 | 144 | if (typeSymbol.TypeKind == TypeKind.Interface) 145 | { 146 | CreateDiagnosticError(GeneratorDiagnosticDescriptors.UserDefinedConversionsToOrFromAnInterfaceAreNotAllowed); 147 | return null; 148 | } 149 | } 150 | 151 | return GenerateClassSource(classSymbol, classSymbol.BaseType.TypeParameters, typeArguments); 152 | 153 | void CreateDiagnosticError(DiagnosticDescriptor descriptor) 154 | { 155 | context.ReportDiagnostic(Diagnostic.Create(descriptor, attributeLocation, classSymbol.Name, 156 | DiagnosticSeverity.Error)); 157 | } 158 | } 159 | 160 | private static string GetGenericPart(ImmutableArray typeArguments) => 161 | string.Join(", ", typeArguments.Select(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); 162 | 163 | private static string? GetOpenGenericPart(INamedTypeSymbol classSymbol) 164 | { 165 | if (!classSymbol.TypeArguments.Any()) 166 | { 167 | return null; 168 | } 169 | 170 | return $"<{GetGenericPart(classSymbol.TypeArguments)}>"; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /OneOf.SourceGenerator/tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve 4 | 5 | foreach($analyzersPath in $analyzersPaths) 6 | { 7 | # Install the language agnostic analyzers. 8 | if (Test-Path $analyzersPath) 9 | { 10 | foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) 11 | { 12 | if($project.Object.AnalyzerReferences) 13 | { 14 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 15 | } 16 | } 17 | } 18 | } 19 | 20 | # $project.Type gives the language name like (C# or VB.NET) 21 | $languageFolder = "" 22 | if($project.Type -eq "C#") 23 | { 24 | $languageFolder = "cs" 25 | } 26 | if($project.Type -eq "VB.NET") 27 | { 28 | $languageFolder = "vb" 29 | } 30 | if($languageFolder -eq "") 31 | { 32 | return 33 | } 34 | 35 | foreach($analyzersPath in $analyzersPaths) 36 | { 37 | # Install language specific analyzers. 38 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 39 | if (Test-Path $languageAnalyzersPath) 40 | { 41 | foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) 42 | { 43 | if($project.Object.AnalyzerReferences) 44 | { 45 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /OneOf.SourceGenerator/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve 4 | 5 | foreach($analyzersPath in $analyzersPaths) 6 | { 7 | # Uninstall the language agnostic analyzers. 8 | if (Test-Path $analyzersPath) 9 | { 10 | foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) 11 | { 12 | if($project.Object.AnalyzerReferences) 13 | { 14 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 15 | } 16 | } 17 | } 18 | } 19 | 20 | # $project.Type gives the language name like (C# or VB.NET) 21 | $languageFolder = "" 22 | if($project.Type -eq "C#") 23 | { 24 | $languageFolder = "cs" 25 | } 26 | if($project.Type -eq "VB.NET") 27 | { 28 | $languageFolder = "vb" 29 | } 30 | if($languageFolder -eq "") 31 | { 32 | return 33 | } 34 | 35 | foreach($analyzersPath in $analyzersPaths) 36 | { 37 | # Uninstall language specific analyzers. 38 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 39 | if (Test-Path $languageAnalyzersPath) 40 | { 41 | foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) 42 | { 43 | if($project.Object.AnalyzerReferences) 44 | { 45 | try 46 | { 47 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 48 | } 49 | catch 50 | { 51 | 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /OneOf.Tests/BaseClassTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace OneOf.Tests { 4 | public class Response : OneOfBase< 5 | Response.MethodNotAllowed, 6 | Response.InvokeSuccessResponse 7 | > 8 | { 9 | Response(OneOf _) : base(_) { } 10 | public class MethodNotAllowed {} 11 | public class InvokeSuccessResponse {} 12 | 13 | public static implicit operator Response(MethodNotAllowed _) => new Response(_); 14 | public static implicit operator Response(InvokeSuccessResponse _) => new Response(_); 15 | } 16 | 17 | public class BaseClassTests 18 | { 19 | [Test] 20 | public void CanMatchOnBase() 21 | { 22 | Response x = new Response.MethodNotAllowed(); 23 | Assert.AreEqual(true, x.Match( 24 | methodNotAllowed => true, 25 | invokeSuccessResponse => false)); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /OneOf.Tests/DefaultConstructorTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using OneOf; 3 | 4 | namespace OneOf.Tests 5 | { 6 | public class DefaultConstructorTests 7 | { 8 | [Test] 9 | public void DefaultConstructorSetsValueToDefaultValueOfT0() 10 | { 11 | var x = new OneOf(); 12 | var result = x.Match(n => n == default(int), n => false); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /OneOf.Tests/EqualsTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using OneOf; 3 | 4 | namespace OneOf.Tests 5 | { 6 | public class EqualsTests 7 | { 8 | [Test] 9 | public void AreEqual() 10 | { 11 | OneOf a = 1; 12 | OneOf b = a; 13 | Assert.IsTrue(a.Equals(b)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /OneOf.Tests/InterfaceTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using OneOf; 3 | 4 | namespace OneOf.Tests 5 | { 6 | public class InterfaceTests 7 | { 8 | [Test] 9 | public void ResolveIFooFromResultMethod() 10 | { 11 | var result = OneOf.FromT0(new Foo()); 12 | } 13 | } 14 | 15 | public class Foo : IFoo 16 | { 17 | 18 | } 19 | public interface IFoo 20 | { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /OneOf.Tests/MapTests.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using NUnit.Framework; 3 | using OneOf; 4 | 5 | namespace OneOf.Tests 6 | { 7 | public class MapTests 8 | { 9 | [Test] 10 | public void MapValue() 11 | { 12 | var resolvedDouble = ResolveString(2.0); 13 | var resolveInt = ResolveString(4); 14 | var resolveString = ResolveString("6"); 15 | } 16 | 17 | private string ResolveString(OneOf input) 18 | => input 19 | .MapT0(d => d.ToString(CultureInfo.InvariantCulture)) 20 | .MapT1(i => i.ToString(CultureInfo.InvariantCulture)) 21 | .Match(t1 => t1, t2 => t2, t3 => t3); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /OneOf.Tests/MixedReferenceAndValueTypeTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace OneOf.Tests 4 | { 5 | public class RetryStrategy : OneOfBase 6 | { 7 | RetryStrategy(OneOf _) : base(_) { } 8 | 9 | public class Never { 10 | internal int _attempts = 0; 11 | } 12 | 13 | public int Attempts => Match(n => n._attempts, n => n); 14 | 15 | public static implicit operator RetryStrategy(Never never) => new RetryStrategy(never); 16 | public static implicit operator RetryStrategy(int attempts) => new RetryStrategy(attempts); 17 | } 18 | 19 | public class MixedReferenceAndValueTypeTests 20 | { 21 | [Test] 22 | public void CanMatchOnReferenceAndValue() 23 | { 24 | RetryStrategy strat = 5; 25 | Assert.AreEqual(strat.Attempts, 5); 26 | strat = new RetryStrategy.Never(); 27 | Assert.AreEqual(strat.Attempts, 0); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /OneOf.Tests/OneOf.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net451 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /OneOf.Tests/OneOfJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using Newtonsoft.Json; 5 | 6 | namespace OneOf 7 | { 8 | public class OneOfJsonConverter : JsonConverter 9 | { 10 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 11 | { 12 | if (value is IOneOf) 13 | { 14 | value = ((IOneOf) value).Value; 15 | } 16 | serializer.Serialize(writer, value); 17 | } 18 | 19 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | 24 | public override bool CanConvert(Type objectType) 25 | { 26 | return objectType.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IOneOf)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /OneOf.Tests/Serialization.cs: -------------------------------------------------------------------------------- 1 | using OneOf; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Security.Policy; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Newtonsoft.Json; 9 | using NUnit.Framework; 10 | 11 | namespace OneOf.Tests 12 | { 13 | public class Serialization 14 | { 15 | [Test] 16 | public void CanSerializeOneOfValueTransparently() 17 | { 18 | //Given an object with a OneOf property 19 | var x = new SomeThing() 20 | { 21 | Value = "A string value" 22 | }; 23 | //When that object is serialized 24 | var json = JsonConvert.SerializeObject(x, new JsonSerializerSettings() 25 | { 26 | Converters = {new OneOfJsonConverter()} 27 | }); 28 | 29 | //Then the OneOfs underlying value should have been written 30 | Assert.AreEqual("{\"Value\":\"A string value\"}", json); 31 | } 32 | } 33 | 34 | class SomeThing 35 | { 36 | public OneOf Value { get; set; } 37 | } 38 | 39 | internal class SomeOtherThing 40 | { 41 | public int Value { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /OneOf.Tests/ToStringTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Threading; 4 | using NUnit.Framework; 5 | 6 | namespace OneOf.Tests 7 | { 8 | public class ToStringTests 9 | { 10 | static string RunInCulture(CultureInfo culture, Func action) 11 | { 12 | var originalCulture = Thread.CurrentThread.CurrentCulture; 13 | Thread.CurrentThread.CurrentCulture = culture; 14 | try 15 | { 16 | return action(); 17 | } 18 | finally 19 | { 20 | Thread.CurrentThread.CurrentCulture = originalCulture; 21 | } 22 | } 23 | 24 | [TestCase("en-NZ", ExpectedResult = "System.DateTime: 2/01/2019 1:02:03 AM")] 25 | [TestCase("en-US", ExpectedResult = "System.DateTime: 1/2/2019 1:02:03 AM")] 26 | public string LeftSideFormatsWithCurrentCulture(string cultureName) 27 | { 28 | return RunInCulture(new CultureInfo(cultureName, false), () => 29 | { 30 | OneOf a = new DateTime(2019, 1, 2, 1, 2, 3); 31 | return a.ToString(); 32 | }); 33 | } 34 | 35 | [TestCase("en-NZ", ExpectedResult = "System.DateTime: 2/01/2019 1:02:03 AM")] 36 | [TestCase("en-US", ExpectedResult = "System.DateTime: 1/2/2019 1:02:03 AM")] 37 | public string RightSideFormatsWithCurrentCulture(string cultureName) 38 | { 39 | return RunInCulture(new CultureInfo(cultureName, false), () => 40 | { 41 | OneOf a = new DateTime(2019, 1, 2, 1, 2, 3); 42 | return a.ToString(); 43 | }); 44 | } 45 | 46 | [Test] 47 | public void TheValueAndTypeNameAreFormattedCorrectly() 48 | { 49 | OneOf a = 42; 50 | Assert.AreEqual("System.Int32: 42", a.ToString()); 51 | } 52 | 53 | public class RecursiveOneOf : OneOfBase 54 | { 55 | RecursiveOneOf(OneOf _) : base(_) { } 56 | public class InnerOne { } 57 | public class InnerTwo { } 58 | } 59 | 60 | [Test] 61 | public void CallingToStringOnARecursiveTypeWorks() 62 | { 63 | var innerTypeOfRecursiveOneOf = new RecursiveOneOf.InnerOne(); 64 | 65 | Assert.AreEqual("OneOf.Tests.ToStringTests+RecursiveOneOf+InnerOne", innerTypeOfRecursiveOneOf.ToString()); 66 | } 67 | 68 | [Test] 69 | public void CallingToStringOnANestedNonRecursiveTypeWorks() 70 | { 71 | OneOf, OneOf> nestedType = (OneOf)true; 72 | 73 | Assert.AreEqual("OneOf.OneOf`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]: System.Boolean: True", nestedType.ToString()); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /OneOf.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32611.2 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneOf.Tests", "OneOf.Tests\OneOf.Tests.csproj", "{82023ED0-3E20-43CF-ACA1-1EA547E70903}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {21D0DD1E-D38A-4A76-B2B3-4CEF11B2C6CB} = {21D0DD1E-D38A-4A76-B2B3-4CEF11B2C6CB} 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneOf", "OneOf\OneOf.csproj", "{21D0DD1E-D38A-4A76-B2B3-4CEF11B2C6CB}" 12 | EndProject 13 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "OneOf.FSharp", "OneOf.FSharp\OneOf.FSharp.fsproj", "{26D54D37-8692-4E2A-9043-D2FFCBF5A816}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneOf.Extended", "OneOf.Extended\OneOf.Extended.csproj", "{FB8845F4-51F1-407A-8E10-2A0B892E17FB}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generator", "Generator\Generator.csproj", "{508CDAF6-E780-459E-BD8F-776A5EE2C2FF}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneOf.SourceGenerator", "OneOf.SourceGenerator\OneOf.SourceGenerator.csproj", "{AC54E93D-1DB2-4143-A1AF-3CE2492EAC83}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneOf.SourceGenerator.Tests", "OneOf.SourceGenerator.Tests\OneOf.SourceGenerator.Tests.csproj", "{A7D18F0E-8966-4685-8146-34F507356F5D}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneOf.SourceGenerator.AnalyzerTests", "OneOf.SourceGenerator.AnalyzerTests\OneOf.SourceGenerator.AnalyzerTests.csproj", "{C08F270E-157A-48B9-A7B6-C948FCFC5494}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {82023ED0-3E20-43CF-ACA1-1EA547E70903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {82023ED0-3E20-43CF-ACA1-1EA547E70903}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {82023ED0-3E20-43CF-ACA1-1EA547E70903}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {82023ED0-3E20-43CF-ACA1-1EA547E70903}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {21D0DD1E-D38A-4A76-B2B3-4CEF11B2C6CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {21D0DD1E-D38A-4A76-B2B3-4CEF11B2C6CB}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {21D0DD1E-D38A-4A76-B2B3-4CEF11B2C6CB}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {21D0DD1E-D38A-4A76-B2B3-4CEF11B2C6CB}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {26D54D37-8692-4E2A-9043-D2FFCBF5A816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {26D54D37-8692-4E2A-9043-D2FFCBF5A816}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {26D54D37-8692-4E2A-9043-D2FFCBF5A816}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {26D54D37-8692-4E2A-9043-D2FFCBF5A816}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {FB8845F4-51F1-407A-8E10-2A0B892E17FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {FB8845F4-51F1-407A-8E10-2A0B892E17FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {FB8845F4-51F1-407A-8E10-2A0B892E17FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {FB8845F4-51F1-407A-8E10-2A0B892E17FB}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {508CDAF6-E780-459E-BD8F-776A5EE2C2FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {508CDAF6-E780-459E-BD8F-776A5EE2C2FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {508CDAF6-E780-459E-BD8F-776A5EE2C2FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {508CDAF6-E780-459E-BD8F-776A5EE2C2FF}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {AC54E93D-1DB2-4143-A1AF-3CE2492EAC83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {AC54E93D-1DB2-4143-A1AF-3CE2492EAC83}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {AC54E93D-1DB2-4143-A1AF-3CE2492EAC83}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {AC54E93D-1DB2-4143-A1AF-3CE2492EAC83}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {A7D18F0E-8966-4685-8146-34F507356F5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {A7D18F0E-8966-4685-8146-34F507356F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {A7D18F0E-8966-4685-8146-34F507356F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {A7D18F0E-8966-4685-8146-34F507356F5D}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {C08F270E-157A-48B9-A7B6-C948FCFC5494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {C08F270E-157A-48B9-A7B6-C948FCFC5494}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {C08F270E-157A-48B9-A7B6-C948FCFC5494}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {C08F270E-157A-48B9-A7B6-C948FCFC5494}.Release|Any CPU.Build.0 = Release|Any CPU 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | GlobalSection(ExtensibilityGlobals) = postSolution 68 | SolutionGuid = {10E10950-5AD9-4F2C-8174-E0F810680317} 69 | EndGlobalSection 70 | EndGlobal 71 | -------------------------------------------------------------------------------- /OneOf/Assembly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [assembly: CLSCompliant(true)] 4 | -------------------------------------------------------------------------------- /OneOf/Functions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OneOf { 4 | internal static class Functions { 5 | internal static string FormatValue(T value) => $"{typeof(T).FullName}: {value?.ToString()}"; 6 | internal static string FormatValue(object @this, object @base, T value) => 7 | ReferenceEquals(@this, value) ? 8 | @base.ToString() : 9 | $"{typeof(T).FullName}: {value?.ToString()}"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /OneOf/IOneOf.cs: -------------------------------------------------------------------------------- 1 | namespace OneOf 2 | { 3 | public interface IOneOf 4 | { 5 | object Value { get ; } 6 | int Index { get; } 7 | } 8 | } -------------------------------------------------------------------------------- /OneOf/OneOf.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net35;net45;netstandard1.3;netstandard2.0 5 | Harry McIntyre 6 | OneOf - Easy Discriminated Unions for c# 7 | Harry McIntyre 8 | 1.0.0 9 | Harry McIntyre 10 | F# style discriminated unions for C#, using a custom type OneOf<T0, ... Tn> which holds a single value and has a .Match(...) method on it for exhaustive matching. Simple but powerful. 11 | https://github.com/mcintyre321/OneOf/ 12 | https://github.com/mcintyre321/OneOf/blob/master/licence.md 13 | discriminated unions, return type, match switch 14 | True 15 | OneOf 16 | Harry McIntyre 17 | true 18 | true 19 | snupkg 20 | 21 | 9.0 22 | enable 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /OneOf/OneOfBaseT0.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public class OneOfBase : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly int _index; 10 | 11 | protected OneOfBase(OneOf input) 12 | { 13 | _index = input.Index; 14 | switch (_index) 15 | { 16 | case 0: _value0 = input.AsT0; break; 17 | default: throw new InvalidOperationException(); 18 | } 19 | } 20 | 21 | public object Value => 22 | _index switch 23 | { 24 | 0 => _value0, 25 | _ => throw new InvalidOperationException() 26 | }; 27 | 28 | public int Index => _index; 29 | 30 | public bool IsT0 => _index == 0; 31 | 32 | public T0 AsT0 => 33 | _index == 0 ? 34 | _value0 : 35 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 36 | 37 | 38 | 39 | public void Switch(Action f0) 40 | { 41 | if (_index == 0 && f0 != null) 42 | { 43 | f0(_value0); 44 | return; 45 | } 46 | throw new InvalidOperationException(); 47 | } 48 | 49 | public TResult Match(Func f0) 50 | { 51 | if (_index == 0 && f0 != null) 52 | { 53 | return f0(_value0); 54 | } 55 | throw new InvalidOperationException(); 56 | } 57 | 58 | 59 | 60 | 61 | 62 | bool Equals(OneOfBase other) => 63 | _index == other._index && 64 | _index switch 65 | { 66 | 0 => Equals(_value0, other._value0), 67 | _ => false 68 | }; 69 | 70 | public override bool Equals(object obj) 71 | { 72 | if (ReferenceEquals(null, obj)) 73 | { 74 | return false; 75 | } 76 | 77 | if (ReferenceEquals(this, obj)) { 78 | return true; 79 | } 80 | 81 | return obj is OneOfBase o && Equals(o); 82 | } 83 | 84 | public override string ToString() => 85 | _index switch { 86 | 0 => FormatValue(_value0), 87 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 88 | }; 89 | 90 | public override int GetHashCode() 91 | { 92 | unchecked 93 | { 94 | int hashCode = _index switch 95 | { 96 | 0 => _value0?.GetHashCode(), 97 | _ => 0 98 | } ?? 0; 99 | return (hashCode*397) ^ _index; 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /OneOf/OneOfBaseT1.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public class OneOfBase : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly int _index; 11 | 12 | protected OneOfBase(OneOf input) 13 | { 14 | _index = input.Index; 15 | switch (_index) 16 | { 17 | case 0: _value0 = input.AsT0; break; 18 | case 1: _value1 = input.AsT1; break; 19 | default: throw new InvalidOperationException(); 20 | } 21 | } 22 | 23 | public object Value => 24 | _index switch 25 | { 26 | 0 => _value0, 27 | 1 => _value1, 28 | _ => throw new InvalidOperationException() 29 | }; 30 | 31 | public int Index => _index; 32 | 33 | public bool IsT0 => _index == 0; 34 | public bool IsT1 => _index == 1; 35 | 36 | public T0 AsT0 => 37 | _index == 0 ? 38 | _value0 : 39 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 40 | public T1 AsT1 => 41 | _index == 1 ? 42 | _value1 : 43 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 44 | 45 | 46 | 47 | public void Switch(Action f0, Action f1) 48 | { 49 | if (_index == 0 && f0 != null) 50 | { 51 | f0(_value0); 52 | return; 53 | } 54 | if (_index == 1 && f1 != null) 55 | { 56 | f1(_value1); 57 | return; 58 | } 59 | throw new InvalidOperationException(); 60 | } 61 | 62 | public TResult Match(Func f0, Func f1) 63 | { 64 | if (_index == 0 && f0 != null) 65 | { 66 | return f0(_value0); 67 | } 68 | if (_index == 1 && f1 != null) 69 | { 70 | return f1(_value1); 71 | } 72 | throw new InvalidOperationException(); 73 | } 74 | 75 | 76 | 77 | 78 | 79 | public bool TryPickT0(out T0 value, out T1 remainder) 80 | { 81 | value = IsT0 ? AsT0 : default; 82 | remainder = _index switch 83 | { 84 | 0 => default, 85 | 1 => AsT1, 86 | _ => throw new InvalidOperationException() 87 | }; 88 | return this.IsT0; 89 | } 90 | 91 | public bool TryPickT1(out T1 value, out T0 remainder) 92 | { 93 | value = IsT1 ? AsT1 : default; 94 | remainder = _index switch 95 | { 96 | 0 => AsT0, 97 | 1 => default, 98 | _ => throw new InvalidOperationException() 99 | }; 100 | return this.IsT1; 101 | } 102 | 103 | bool Equals(OneOfBase other) => 104 | _index == other._index && 105 | _index switch 106 | { 107 | 0 => Equals(_value0, other._value0), 108 | 1 => Equals(_value1, other._value1), 109 | _ => false 110 | }; 111 | 112 | public override bool Equals(object obj) 113 | { 114 | if (ReferenceEquals(null, obj)) 115 | { 116 | return false; 117 | } 118 | 119 | if (ReferenceEquals(this, obj)) { 120 | return true; 121 | } 122 | 123 | return obj is OneOfBase o && Equals(o); 124 | } 125 | 126 | public override string ToString() => 127 | _index switch { 128 | 0 => FormatValue(_value0), 129 | 1 => FormatValue(_value1), 130 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 131 | }; 132 | 133 | public override int GetHashCode() 134 | { 135 | unchecked 136 | { 137 | int hashCode = _index switch 138 | { 139 | 0 => _value0?.GetHashCode(), 140 | 1 => _value1?.GetHashCode(), 141 | _ => 0 142 | } ?? 0; 143 | return (hashCode*397) ^ _index; 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /OneOf/OneOfBaseT2.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public class OneOfBase : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly int _index; 12 | 13 | protected OneOfBase(OneOf input) 14 | { 15 | _index = input.Index; 16 | switch (_index) 17 | { 18 | case 0: _value0 = input.AsT0; break; 19 | case 1: _value1 = input.AsT1; break; 20 | case 2: _value2 = input.AsT2; break; 21 | default: throw new InvalidOperationException(); 22 | } 23 | } 24 | 25 | public object Value => 26 | _index switch 27 | { 28 | 0 => _value0, 29 | 1 => _value1, 30 | 2 => _value2, 31 | _ => throw new InvalidOperationException() 32 | }; 33 | 34 | public int Index => _index; 35 | 36 | public bool IsT0 => _index == 0; 37 | public bool IsT1 => _index == 1; 38 | public bool IsT2 => _index == 2; 39 | 40 | public T0 AsT0 => 41 | _index == 0 ? 42 | _value0 : 43 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 44 | public T1 AsT1 => 45 | _index == 1 ? 46 | _value1 : 47 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 48 | public T2 AsT2 => 49 | _index == 2 ? 50 | _value2 : 51 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 52 | 53 | 54 | 55 | public void Switch(Action f0, Action f1, Action f2) 56 | { 57 | if (_index == 0 && f0 != null) 58 | { 59 | f0(_value0); 60 | return; 61 | } 62 | if (_index == 1 && f1 != null) 63 | { 64 | f1(_value1); 65 | return; 66 | } 67 | if (_index == 2 && f2 != null) 68 | { 69 | f2(_value2); 70 | return; 71 | } 72 | throw new InvalidOperationException(); 73 | } 74 | 75 | public TResult Match(Func f0, Func f1, Func f2) 76 | { 77 | if (_index == 0 && f0 != null) 78 | { 79 | return f0(_value0); 80 | } 81 | if (_index == 1 && f1 != null) 82 | { 83 | return f1(_value1); 84 | } 85 | if (_index == 2 && f2 != null) 86 | { 87 | return f2(_value2); 88 | } 89 | throw new InvalidOperationException(); 90 | } 91 | 92 | 93 | 94 | 95 | 96 | public bool TryPickT0(out T0 value, out OneOf remainder) 97 | { 98 | value = IsT0 ? AsT0 : default; 99 | remainder = _index switch 100 | { 101 | 0 => default, 102 | 1 => AsT1, 103 | 2 => AsT2, 104 | _ => throw new InvalidOperationException() 105 | }; 106 | return this.IsT0; 107 | } 108 | 109 | public bool TryPickT1(out T1 value, out OneOf remainder) 110 | { 111 | value = IsT1 ? AsT1 : default; 112 | remainder = _index switch 113 | { 114 | 0 => AsT0, 115 | 1 => default, 116 | 2 => AsT2, 117 | _ => throw new InvalidOperationException() 118 | }; 119 | return this.IsT1; 120 | } 121 | 122 | public bool TryPickT2(out T2 value, out OneOf remainder) 123 | { 124 | value = IsT2 ? AsT2 : default; 125 | remainder = _index switch 126 | { 127 | 0 => AsT0, 128 | 1 => AsT1, 129 | 2 => default, 130 | _ => throw new InvalidOperationException() 131 | }; 132 | return this.IsT2; 133 | } 134 | 135 | bool Equals(OneOfBase other) => 136 | _index == other._index && 137 | _index switch 138 | { 139 | 0 => Equals(_value0, other._value0), 140 | 1 => Equals(_value1, other._value1), 141 | 2 => Equals(_value2, other._value2), 142 | _ => false 143 | }; 144 | 145 | public override bool Equals(object obj) 146 | { 147 | if (ReferenceEquals(null, obj)) 148 | { 149 | return false; 150 | } 151 | 152 | if (ReferenceEquals(this, obj)) { 153 | return true; 154 | } 155 | 156 | return obj is OneOfBase o && Equals(o); 157 | } 158 | 159 | public override string ToString() => 160 | _index switch { 161 | 0 => FormatValue(_value0), 162 | 1 => FormatValue(_value1), 163 | 2 => FormatValue(_value2), 164 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 165 | }; 166 | 167 | public override int GetHashCode() 168 | { 169 | unchecked 170 | { 171 | int hashCode = _index switch 172 | { 173 | 0 => _value0?.GetHashCode(), 174 | 1 => _value1?.GetHashCode(), 175 | 2 => _value2?.GetHashCode(), 176 | _ => 0 177 | } ?? 0; 178 | return (hashCode*397) ^ _index; 179 | } 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /OneOf/OneOfBaseT3.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public class OneOfBase : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly T3 _value3; 12 | readonly int _index; 13 | 14 | protected OneOfBase(OneOf input) 15 | { 16 | _index = input.Index; 17 | switch (_index) 18 | { 19 | case 0: _value0 = input.AsT0; break; 20 | case 1: _value1 = input.AsT1; break; 21 | case 2: _value2 = input.AsT2; break; 22 | case 3: _value3 = input.AsT3; break; 23 | default: throw new InvalidOperationException(); 24 | } 25 | } 26 | 27 | public object Value => 28 | _index switch 29 | { 30 | 0 => _value0, 31 | 1 => _value1, 32 | 2 => _value2, 33 | 3 => _value3, 34 | _ => throw new InvalidOperationException() 35 | }; 36 | 37 | public int Index => _index; 38 | 39 | public bool IsT0 => _index == 0; 40 | public bool IsT1 => _index == 1; 41 | public bool IsT2 => _index == 2; 42 | public bool IsT3 => _index == 3; 43 | 44 | public T0 AsT0 => 45 | _index == 0 ? 46 | _value0 : 47 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 48 | public T1 AsT1 => 49 | _index == 1 ? 50 | _value1 : 51 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 52 | public T2 AsT2 => 53 | _index == 2 ? 54 | _value2 : 55 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 56 | public T3 AsT3 => 57 | _index == 3 ? 58 | _value3 : 59 | throw new InvalidOperationException($"Cannot return as T3 as result is T{_index}"); 60 | 61 | 62 | 63 | public void Switch(Action f0, Action f1, Action f2, Action f3) 64 | { 65 | if (_index == 0 && f0 != null) 66 | { 67 | f0(_value0); 68 | return; 69 | } 70 | if (_index == 1 && f1 != null) 71 | { 72 | f1(_value1); 73 | return; 74 | } 75 | if (_index == 2 && f2 != null) 76 | { 77 | f2(_value2); 78 | return; 79 | } 80 | if (_index == 3 && f3 != null) 81 | { 82 | f3(_value3); 83 | return; 84 | } 85 | throw new InvalidOperationException(); 86 | } 87 | 88 | public TResult Match(Func f0, Func f1, Func f2, Func f3) 89 | { 90 | if (_index == 0 && f0 != null) 91 | { 92 | return f0(_value0); 93 | } 94 | if (_index == 1 && f1 != null) 95 | { 96 | return f1(_value1); 97 | } 98 | if (_index == 2 && f2 != null) 99 | { 100 | return f2(_value2); 101 | } 102 | if (_index == 3 && f3 != null) 103 | { 104 | return f3(_value3); 105 | } 106 | throw new InvalidOperationException(); 107 | } 108 | 109 | 110 | 111 | 112 | 113 | public bool TryPickT0(out T0 value, out OneOf remainder) 114 | { 115 | value = IsT0 ? AsT0 : default; 116 | remainder = _index switch 117 | { 118 | 0 => default, 119 | 1 => AsT1, 120 | 2 => AsT2, 121 | 3 => AsT3, 122 | _ => throw new InvalidOperationException() 123 | }; 124 | return this.IsT0; 125 | } 126 | 127 | public bool TryPickT1(out T1 value, out OneOf remainder) 128 | { 129 | value = IsT1 ? AsT1 : default; 130 | remainder = _index switch 131 | { 132 | 0 => AsT0, 133 | 1 => default, 134 | 2 => AsT2, 135 | 3 => AsT3, 136 | _ => throw new InvalidOperationException() 137 | }; 138 | return this.IsT1; 139 | } 140 | 141 | public bool TryPickT2(out T2 value, out OneOf remainder) 142 | { 143 | value = IsT2 ? AsT2 : default; 144 | remainder = _index switch 145 | { 146 | 0 => AsT0, 147 | 1 => AsT1, 148 | 2 => default, 149 | 3 => AsT3, 150 | _ => throw new InvalidOperationException() 151 | }; 152 | return this.IsT2; 153 | } 154 | 155 | public bool TryPickT3(out T3 value, out OneOf remainder) 156 | { 157 | value = IsT3 ? AsT3 : default; 158 | remainder = _index switch 159 | { 160 | 0 => AsT0, 161 | 1 => AsT1, 162 | 2 => AsT2, 163 | 3 => default, 164 | _ => throw new InvalidOperationException() 165 | }; 166 | return this.IsT3; 167 | } 168 | 169 | bool Equals(OneOfBase other) => 170 | _index == other._index && 171 | _index switch 172 | { 173 | 0 => Equals(_value0, other._value0), 174 | 1 => Equals(_value1, other._value1), 175 | 2 => Equals(_value2, other._value2), 176 | 3 => Equals(_value3, other._value3), 177 | _ => false 178 | }; 179 | 180 | public override bool Equals(object obj) 181 | { 182 | if (ReferenceEquals(null, obj)) 183 | { 184 | return false; 185 | } 186 | 187 | if (ReferenceEquals(this, obj)) { 188 | return true; 189 | } 190 | 191 | return obj is OneOfBase o && Equals(o); 192 | } 193 | 194 | public override string ToString() => 195 | _index switch { 196 | 0 => FormatValue(_value0), 197 | 1 => FormatValue(_value1), 198 | 2 => FormatValue(_value2), 199 | 3 => FormatValue(_value3), 200 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 201 | }; 202 | 203 | public override int GetHashCode() 204 | { 205 | unchecked 206 | { 207 | int hashCode = _index switch 208 | { 209 | 0 => _value0?.GetHashCode(), 210 | 1 => _value1?.GetHashCode(), 211 | 2 => _value2?.GetHashCode(), 212 | 3 => _value3?.GetHashCode(), 213 | _ => 0 214 | } ?? 0; 215 | return (hashCode*397) ^ _index; 216 | } 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /OneOf/OneOfBaseT4.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public class OneOfBase : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly T3 _value3; 12 | readonly T4 _value4; 13 | readonly int _index; 14 | 15 | protected OneOfBase(OneOf input) 16 | { 17 | _index = input.Index; 18 | switch (_index) 19 | { 20 | case 0: _value0 = input.AsT0; break; 21 | case 1: _value1 = input.AsT1; break; 22 | case 2: _value2 = input.AsT2; break; 23 | case 3: _value3 = input.AsT3; break; 24 | case 4: _value4 = input.AsT4; break; 25 | default: throw new InvalidOperationException(); 26 | } 27 | } 28 | 29 | public object Value => 30 | _index switch 31 | { 32 | 0 => _value0, 33 | 1 => _value1, 34 | 2 => _value2, 35 | 3 => _value3, 36 | 4 => _value4, 37 | _ => throw new InvalidOperationException() 38 | }; 39 | 40 | public int Index => _index; 41 | 42 | public bool IsT0 => _index == 0; 43 | public bool IsT1 => _index == 1; 44 | public bool IsT2 => _index == 2; 45 | public bool IsT3 => _index == 3; 46 | public bool IsT4 => _index == 4; 47 | 48 | public T0 AsT0 => 49 | _index == 0 ? 50 | _value0 : 51 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 52 | public T1 AsT1 => 53 | _index == 1 ? 54 | _value1 : 55 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 56 | public T2 AsT2 => 57 | _index == 2 ? 58 | _value2 : 59 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 60 | public T3 AsT3 => 61 | _index == 3 ? 62 | _value3 : 63 | throw new InvalidOperationException($"Cannot return as T3 as result is T{_index}"); 64 | public T4 AsT4 => 65 | _index == 4 ? 66 | _value4 : 67 | throw new InvalidOperationException($"Cannot return as T4 as result is T{_index}"); 68 | 69 | 70 | 71 | public void Switch(Action f0, Action f1, Action f2, Action f3, Action f4) 72 | { 73 | if (_index == 0 && f0 != null) 74 | { 75 | f0(_value0); 76 | return; 77 | } 78 | if (_index == 1 && f1 != null) 79 | { 80 | f1(_value1); 81 | return; 82 | } 83 | if (_index == 2 && f2 != null) 84 | { 85 | f2(_value2); 86 | return; 87 | } 88 | if (_index == 3 && f3 != null) 89 | { 90 | f3(_value3); 91 | return; 92 | } 93 | if (_index == 4 && f4 != null) 94 | { 95 | f4(_value4); 96 | return; 97 | } 98 | throw new InvalidOperationException(); 99 | } 100 | 101 | public TResult Match(Func f0, Func f1, Func f2, Func f3, Func f4) 102 | { 103 | if (_index == 0 && f0 != null) 104 | { 105 | return f0(_value0); 106 | } 107 | if (_index == 1 && f1 != null) 108 | { 109 | return f1(_value1); 110 | } 111 | if (_index == 2 && f2 != null) 112 | { 113 | return f2(_value2); 114 | } 115 | if (_index == 3 && f3 != null) 116 | { 117 | return f3(_value3); 118 | } 119 | if (_index == 4 && f4 != null) 120 | { 121 | return f4(_value4); 122 | } 123 | throw new InvalidOperationException(); 124 | } 125 | 126 | 127 | 128 | 129 | 130 | public bool TryPickT0(out T0 value, out OneOf remainder) 131 | { 132 | value = IsT0 ? AsT0 : default; 133 | remainder = _index switch 134 | { 135 | 0 => default, 136 | 1 => AsT1, 137 | 2 => AsT2, 138 | 3 => AsT3, 139 | 4 => AsT4, 140 | _ => throw new InvalidOperationException() 141 | }; 142 | return this.IsT0; 143 | } 144 | 145 | public bool TryPickT1(out T1 value, out OneOf remainder) 146 | { 147 | value = IsT1 ? AsT1 : default; 148 | remainder = _index switch 149 | { 150 | 0 => AsT0, 151 | 1 => default, 152 | 2 => AsT2, 153 | 3 => AsT3, 154 | 4 => AsT4, 155 | _ => throw new InvalidOperationException() 156 | }; 157 | return this.IsT1; 158 | } 159 | 160 | public bool TryPickT2(out T2 value, out OneOf remainder) 161 | { 162 | value = IsT2 ? AsT2 : default; 163 | remainder = _index switch 164 | { 165 | 0 => AsT0, 166 | 1 => AsT1, 167 | 2 => default, 168 | 3 => AsT3, 169 | 4 => AsT4, 170 | _ => throw new InvalidOperationException() 171 | }; 172 | return this.IsT2; 173 | } 174 | 175 | public bool TryPickT3(out T3 value, out OneOf remainder) 176 | { 177 | value = IsT3 ? AsT3 : default; 178 | remainder = _index switch 179 | { 180 | 0 => AsT0, 181 | 1 => AsT1, 182 | 2 => AsT2, 183 | 3 => default, 184 | 4 => AsT4, 185 | _ => throw new InvalidOperationException() 186 | }; 187 | return this.IsT3; 188 | } 189 | 190 | public bool TryPickT4(out T4 value, out OneOf remainder) 191 | { 192 | value = IsT4 ? AsT4 : default; 193 | remainder = _index switch 194 | { 195 | 0 => AsT0, 196 | 1 => AsT1, 197 | 2 => AsT2, 198 | 3 => AsT3, 199 | 4 => default, 200 | _ => throw new InvalidOperationException() 201 | }; 202 | return this.IsT4; 203 | } 204 | 205 | bool Equals(OneOfBase other) => 206 | _index == other._index && 207 | _index switch 208 | { 209 | 0 => Equals(_value0, other._value0), 210 | 1 => Equals(_value1, other._value1), 211 | 2 => Equals(_value2, other._value2), 212 | 3 => Equals(_value3, other._value3), 213 | 4 => Equals(_value4, other._value4), 214 | _ => false 215 | }; 216 | 217 | public override bool Equals(object obj) 218 | { 219 | if (ReferenceEquals(null, obj)) 220 | { 221 | return false; 222 | } 223 | 224 | if (ReferenceEquals(this, obj)) { 225 | return true; 226 | } 227 | 228 | return obj is OneOfBase o && Equals(o); 229 | } 230 | 231 | public override string ToString() => 232 | _index switch { 233 | 0 => FormatValue(_value0), 234 | 1 => FormatValue(_value1), 235 | 2 => FormatValue(_value2), 236 | 3 => FormatValue(_value3), 237 | 4 => FormatValue(_value4), 238 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 239 | }; 240 | 241 | public override int GetHashCode() 242 | { 243 | unchecked 244 | { 245 | int hashCode = _index switch 246 | { 247 | 0 => _value0?.GetHashCode(), 248 | 1 => _value1?.GetHashCode(), 249 | 2 => _value2?.GetHashCode(), 250 | 3 => _value3?.GetHashCode(), 251 | 4 => _value4?.GetHashCode(), 252 | _ => 0 253 | } ?? 0; 254 | return (hashCode*397) ^ _index; 255 | } 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /OneOf/OneOfBaseT5.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public class OneOfBase : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly T3 _value3; 12 | readonly T4 _value4; 13 | readonly T5 _value5; 14 | readonly int _index; 15 | 16 | protected OneOfBase(OneOf input) 17 | { 18 | _index = input.Index; 19 | switch (_index) 20 | { 21 | case 0: _value0 = input.AsT0; break; 22 | case 1: _value1 = input.AsT1; break; 23 | case 2: _value2 = input.AsT2; break; 24 | case 3: _value3 = input.AsT3; break; 25 | case 4: _value4 = input.AsT4; break; 26 | case 5: _value5 = input.AsT5; break; 27 | default: throw new InvalidOperationException(); 28 | } 29 | } 30 | 31 | public object Value => 32 | _index switch 33 | { 34 | 0 => _value0, 35 | 1 => _value1, 36 | 2 => _value2, 37 | 3 => _value3, 38 | 4 => _value4, 39 | 5 => _value5, 40 | _ => throw new InvalidOperationException() 41 | }; 42 | 43 | public int Index => _index; 44 | 45 | public bool IsT0 => _index == 0; 46 | public bool IsT1 => _index == 1; 47 | public bool IsT2 => _index == 2; 48 | public bool IsT3 => _index == 3; 49 | public bool IsT4 => _index == 4; 50 | public bool IsT5 => _index == 5; 51 | 52 | public T0 AsT0 => 53 | _index == 0 ? 54 | _value0 : 55 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 56 | public T1 AsT1 => 57 | _index == 1 ? 58 | _value1 : 59 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 60 | public T2 AsT2 => 61 | _index == 2 ? 62 | _value2 : 63 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 64 | public T3 AsT3 => 65 | _index == 3 ? 66 | _value3 : 67 | throw new InvalidOperationException($"Cannot return as T3 as result is T{_index}"); 68 | public T4 AsT4 => 69 | _index == 4 ? 70 | _value4 : 71 | throw new InvalidOperationException($"Cannot return as T4 as result is T{_index}"); 72 | public T5 AsT5 => 73 | _index == 5 ? 74 | _value5 : 75 | throw new InvalidOperationException($"Cannot return as T5 as result is T{_index}"); 76 | 77 | 78 | 79 | public void Switch(Action f0, Action f1, Action f2, Action f3, Action f4, Action f5) 80 | { 81 | if (_index == 0 && f0 != null) 82 | { 83 | f0(_value0); 84 | return; 85 | } 86 | if (_index == 1 && f1 != null) 87 | { 88 | f1(_value1); 89 | return; 90 | } 91 | if (_index == 2 && f2 != null) 92 | { 93 | f2(_value2); 94 | return; 95 | } 96 | if (_index == 3 && f3 != null) 97 | { 98 | f3(_value3); 99 | return; 100 | } 101 | if (_index == 4 && f4 != null) 102 | { 103 | f4(_value4); 104 | return; 105 | } 106 | if (_index == 5 && f5 != null) 107 | { 108 | f5(_value5); 109 | return; 110 | } 111 | throw new InvalidOperationException(); 112 | } 113 | 114 | public TResult Match(Func f0, Func f1, Func f2, Func f3, Func f4, Func f5) 115 | { 116 | if (_index == 0 && f0 != null) 117 | { 118 | return f0(_value0); 119 | } 120 | if (_index == 1 && f1 != null) 121 | { 122 | return f1(_value1); 123 | } 124 | if (_index == 2 && f2 != null) 125 | { 126 | return f2(_value2); 127 | } 128 | if (_index == 3 && f3 != null) 129 | { 130 | return f3(_value3); 131 | } 132 | if (_index == 4 && f4 != null) 133 | { 134 | return f4(_value4); 135 | } 136 | if (_index == 5 && f5 != null) 137 | { 138 | return f5(_value5); 139 | } 140 | throw new InvalidOperationException(); 141 | } 142 | 143 | 144 | 145 | 146 | 147 | public bool TryPickT0(out T0 value, out OneOf remainder) 148 | { 149 | value = IsT0 ? AsT0 : default; 150 | remainder = _index switch 151 | { 152 | 0 => default, 153 | 1 => AsT1, 154 | 2 => AsT2, 155 | 3 => AsT3, 156 | 4 => AsT4, 157 | 5 => AsT5, 158 | _ => throw new InvalidOperationException() 159 | }; 160 | return this.IsT0; 161 | } 162 | 163 | public bool TryPickT1(out T1 value, out OneOf remainder) 164 | { 165 | value = IsT1 ? AsT1 : default; 166 | remainder = _index switch 167 | { 168 | 0 => AsT0, 169 | 1 => default, 170 | 2 => AsT2, 171 | 3 => AsT3, 172 | 4 => AsT4, 173 | 5 => AsT5, 174 | _ => throw new InvalidOperationException() 175 | }; 176 | return this.IsT1; 177 | } 178 | 179 | public bool TryPickT2(out T2 value, out OneOf remainder) 180 | { 181 | value = IsT2 ? AsT2 : default; 182 | remainder = _index switch 183 | { 184 | 0 => AsT0, 185 | 1 => AsT1, 186 | 2 => default, 187 | 3 => AsT3, 188 | 4 => AsT4, 189 | 5 => AsT5, 190 | _ => throw new InvalidOperationException() 191 | }; 192 | return this.IsT2; 193 | } 194 | 195 | public bool TryPickT3(out T3 value, out OneOf remainder) 196 | { 197 | value = IsT3 ? AsT3 : default; 198 | remainder = _index switch 199 | { 200 | 0 => AsT0, 201 | 1 => AsT1, 202 | 2 => AsT2, 203 | 3 => default, 204 | 4 => AsT4, 205 | 5 => AsT5, 206 | _ => throw new InvalidOperationException() 207 | }; 208 | return this.IsT3; 209 | } 210 | 211 | public bool TryPickT4(out T4 value, out OneOf remainder) 212 | { 213 | value = IsT4 ? AsT4 : default; 214 | remainder = _index switch 215 | { 216 | 0 => AsT0, 217 | 1 => AsT1, 218 | 2 => AsT2, 219 | 3 => AsT3, 220 | 4 => default, 221 | 5 => AsT5, 222 | _ => throw new InvalidOperationException() 223 | }; 224 | return this.IsT4; 225 | } 226 | 227 | public bool TryPickT5(out T5 value, out OneOf remainder) 228 | { 229 | value = IsT5 ? AsT5 : default; 230 | remainder = _index switch 231 | { 232 | 0 => AsT0, 233 | 1 => AsT1, 234 | 2 => AsT2, 235 | 3 => AsT3, 236 | 4 => AsT4, 237 | 5 => default, 238 | _ => throw new InvalidOperationException() 239 | }; 240 | return this.IsT5; 241 | } 242 | 243 | bool Equals(OneOfBase other) => 244 | _index == other._index && 245 | _index switch 246 | { 247 | 0 => Equals(_value0, other._value0), 248 | 1 => Equals(_value1, other._value1), 249 | 2 => Equals(_value2, other._value2), 250 | 3 => Equals(_value3, other._value3), 251 | 4 => Equals(_value4, other._value4), 252 | 5 => Equals(_value5, other._value5), 253 | _ => false 254 | }; 255 | 256 | public override bool Equals(object obj) 257 | { 258 | if (ReferenceEquals(null, obj)) 259 | { 260 | return false; 261 | } 262 | 263 | if (ReferenceEquals(this, obj)) { 264 | return true; 265 | } 266 | 267 | return obj is OneOfBase o && Equals(o); 268 | } 269 | 270 | public override string ToString() => 271 | _index switch { 272 | 0 => FormatValue(_value0), 273 | 1 => FormatValue(_value1), 274 | 2 => FormatValue(_value2), 275 | 3 => FormatValue(_value3), 276 | 4 => FormatValue(_value4), 277 | 5 => FormatValue(_value5), 278 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 279 | }; 280 | 281 | public override int GetHashCode() 282 | { 283 | unchecked 284 | { 285 | int hashCode = _index switch 286 | { 287 | 0 => _value0?.GetHashCode(), 288 | 1 => _value1?.GetHashCode(), 289 | 2 => _value2?.GetHashCode(), 290 | 3 => _value3?.GetHashCode(), 291 | 4 => _value4?.GetHashCode(), 292 | 5 => _value5?.GetHashCode(), 293 | _ => 0 294 | } ?? 0; 295 | return (hashCode*397) ^ _index; 296 | } 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /OneOf/OneOfBaseT6.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public class OneOfBase : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly T3 _value3; 12 | readonly T4 _value4; 13 | readonly T5 _value5; 14 | readonly T6 _value6; 15 | readonly int _index; 16 | 17 | protected OneOfBase(OneOf input) 18 | { 19 | _index = input.Index; 20 | switch (_index) 21 | { 22 | case 0: _value0 = input.AsT0; break; 23 | case 1: _value1 = input.AsT1; break; 24 | case 2: _value2 = input.AsT2; break; 25 | case 3: _value3 = input.AsT3; break; 26 | case 4: _value4 = input.AsT4; break; 27 | case 5: _value5 = input.AsT5; break; 28 | case 6: _value6 = input.AsT6; break; 29 | default: throw new InvalidOperationException(); 30 | } 31 | } 32 | 33 | public object Value => 34 | _index switch 35 | { 36 | 0 => _value0, 37 | 1 => _value1, 38 | 2 => _value2, 39 | 3 => _value3, 40 | 4 => _value4, 41 | 5 => _value5, 42 | 6 => _value6, 43 | _ => throw new InvalidOperationException() 44 | }; 45 | 46 | public int Index => _index; 47 | 48 | public bool IsT0 => _index == 0; 49 | public bool IsT1 => _index == 1; 50 | public bool IsT2 => _index == 2; 51 | public bool IsT3 => _index == 3; 52 | public bool IsT4 => _index == 4; 53 | public bool IsT5 => _index == 5; 54 | public bool IsT6 => _index == 6; 55 | 56 | public T0 AsT0 => 57 | _index == 0 ? 58 | _value0 : 59 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 60 | public T1 AsT1 => 61 | _index == 1 ? 62 | _value1 : 63 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 64 | public T2 AsT2 => 65 | _index == 2 ? 66 | _value2 : 67 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 68 | public T3 AsT3 => 69 | _index == 3 ? 70 | _value3 : 71 | throw new InvalidOperationException($"Cannot return as T3 as result is T{_index}"); 72 | public T4 AsT4 => 73 | _index == 4 ? 74 | _value4 : 75 | throw new InvalidOperationException($"Cannot return as T4 as result is T{_index}"); 76 | public T5 AsT5 => 77 | _index == 5 ? 78 | _value5 : 79 | throw new InvalidOperationException($"Cannot return as T5 as result is T{_index}"); 80 | public T6 AsT6 => 81 | _index == 6 ? 82 | _value6 : 83 | throw new InvalidOperationException($"Cannot return as T6 as result is T{_index}"); 84 | 85 | 86 | 87 | public void Switch(Action f0, Action f1, Action f2, Action f3, Action f4, Action f5, Action f6) 88 | { 89 | if (_index == 0 && f0 != null) 90 | { 91 | f0(_value0); 92 | return; 93 | } 94 | if (_index == 1 && f1 != null) 95 | { 96 | f1(_value1); 97 | return; 98 | } 99 | if (_index == 2 && f2 != null) 100 | { 101 | f2(_value2); 102 | return; 103 | } 104 | if (_index == 3 && f3 != null) 105 | { 106 | f3(_value3); 107 | return; 108 | } 109 | if (_index == 4 && f4 != null) 110 | { 111 | f4(_value4); 112 | return; 113 | } 114 | if (_index == 5 && f5 != null) 115 | { 116 | f5(_value5); 117 | return; 118 | } 119 | if (_index == 6 && f6 != null) 120 | { 121 | f6(_value6); 122 | return; 123 | } 124 | throw new InvalidOperationException(); 125 | } 126 | 127 | public TResult Match(Func f0, Func f1, Func f2, Func f3, Func f4, Func f5, Func f6) 128 | { 129 | if (_index == 0 && f0 != null) 130 | { 131 | return f0(_value0); 132 | } 133 | if (_index == 1 && f1 != null) 134 | { 135 | return f1(_value1); 136 | } 137 | if (_index == 2 && f2 != null) 138 | { 139 | return f2(_value2); 140 | } 141 | if (_index == 3 && f3 != null) 142 | { 143 | return f3(_value3); 144 | } 145 | if (_index == 4 && f4 != null) 146 | { 147 | return f4(_value4); 148 | } 149 | if (_index == 5 && f5 != null) 150 | { 151 | return f5(_value5); 152 | } 153 | if (_index == 6 && f6 != null) 154 | { 155 | return f6(_value6); 156 | } 157 | throw new InvalidOperationException(); 158 | } 159 | 160 | 161 | 162 | 163 | 164 | public bool TryPickT0(out T0 value, out OneOf remainder) 165 | { 166 | value = IsT0 ? AsT0 : default; 167 | remainder = _index switch 168 | { 169 | 0 => default, 170 | 1 => AsT1, 171 | 2 => AsT2, 172 | 3 => AsT3, 173 | 4 => AsT4, 174 | 5 => AsT5, 175 | 6 => AsT6, 176 | _ => throw new InvalidOperationException() 177 | }; 178 | return this.IsT0; 179 | } 180 | 181 | public bool TryPickT1(out T1 value, out OneOf remainder) 182 | { 183 | value = IsT1 ? AsT1 : default; 184 | remainder = _index switch 185 | { 186 | 0 => AsT0, 187 | 1 => default, 188 | 2 => AsT2, 189 | 3 => AsT3, 190 | 4 => AsT4, 191 | 5 => AsT5, 192 | 6 => AsT6, 193 | _ => throw new InvalidOperationException() 194 | }; 195 | return this.IsT1; 196 | } 197 | 198 | public bool TryPickT2(out T2 value, out OneOf remainder) 199 | { 200 | value = IsT2 ? AsT2 : default; 201 | remainder = _index switch 202 | { 203 | 0 => AsT0, 204 | 1 => AsT1, 205 | 2 => default, 206 | 3 => AsT3, 207 | 4 => AsT4, 208 | 5 => AsT5, 209 | 6 => AsT6, 210 | _ => throw new InvalidOperationException() 211 | }; 212 | return this.IsT2; 213 | } 214 | 215 | public bool TryPickT3(out T3 value, out OneOf remainder) 216 | { 217 | value = IsT3 ? AsT3 : default; 218 | remainder = _index switch 219 | { 220 | 0 => AsT0, 221 | 1 => AsT1, 222 | 2 => AsT2, 223 | 3 => default, 224 | 4 => AsT4, 225 | 5 => AsT5, 226 | 6 => AsT6, 227 | _ => throw new InvalidOperationException() 228 | }; 229 | return this.IsT3; 230 | } 231 | 232 | public bool TryPickT4(out T4 value, out OneOf remainder) 233 | { 234 | value = IsT4 ? AsT4 : default; 235 | remainder = _index switch 236 | { 237 | 0 => AsT0, 238 | 1 => AsT1, 239 | 2 => AsT2, 240 | 3 => AsT3, 241 | 4 => default, 242 | 5 => AsT5, 243 | 6 => AsT6, 244 | _ => throw new InvalidOperationException() 245 | }; 246 | return this.IsT4; 247 | } 248 | 249 | public bool TryPickT5(out T5 value, out OneOf remainder) 250 | { 251 | value = IsT5 ? AsT5 : default; 252 | remainder = _index switch 253 | { 254 | 0 => AsT0, 255 | 1 => AsT1, 256 | 2 => AsT2, 257 | 3 => AsT3, 258 | 4 => AsT4, 259 | 5 => default, 260 | 6 => AsT6, 261 | _ => throw new InvalidOperationException() 262 | }; 263 | return this.IsT5; 264 | } 265 | 266 | public bool TryPickT6(out T6 value, out OneOf remainder) 267 | { 268 | value = IsT6 ? AsT6 : default; 269 | remainder = _index switch 270 | { 271 | 0 => AsT0, 272 | 1 => AsT1, 273 | 2 => AsT2, 274 | 3 => AsT3, 275 | 4 => AsT4, 276 | 5 => AsT5, 277 | 6 => default, 278 | _ => throw new InvalidOperationException() 279 | }; 280 | return this.IsT6; 281 | } 282 | 283 | bool Equals(OneOfBase other) => 284 | _index == other._index && 285 | _index switch 286 | { 287 | 0 => Equals(_value0, other._value0), 288 | 1 => Equals(_value1, other._value1), 289 | 2 => Equals(_value2, other._value2), 290 | 3 => Equals(_value3, other._value3), 291 | 4 => Equals(_value4, other._value4), 292 | 5 => Equals(_value5, other._value5), 293 | 6 => Equals(_value6, other._value6), 294 | _ => false 295 | }; 296 | 297 | public override bool Equals(object obj) 298 | { 299 | if (ReferenceEquals(null, obj)) 300 | { 301 | return false; 302 | } 303 | 304 | if (ReferenceEquals(this, obj)) { 305 | return true; 306 | } 307 | 308 | return obj is OneOfBase o && Equals(o); 309 | } 310 | 311 | public override string ToString() => 312 | _index switch { 313 | 0 => FormatValue(_value0), 314 | 1 => FormatValue(_value1), 315 | 2 => FormatValue(_value2), 316 | 3 => FormatValue(_value3), 317 | 4 => FormatValue(_value4), 318 | 5 => FormatValue(_value5), 319 | 6 => FormatValue(_value6), 320 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 321 | }; 322 | 323 | public override int GetHashCode() 324 | { 325 | unchecked 326 | { 327 | int hashCode = _index switch 328 | { 329 | 0 => _value0?.GetHashCode(), 330 | 1 => _value1?.GetHashCode(), 331 | 2 => _value2?.GetHashCode(), 332 | 3 => _value3?.GetHashCode(), 333 | 4 => _value4?.GetHashCode(), 334 | 5 => _value5?.GetHashCode(), 335 | 6 => _value6?.GetHashCode(), 336 | _ => 0 337 | } ?? 0; 338 | return (hashCode*397) ^ _index; 339 | } 340 | } 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /OneOf/OneOfBaseT7.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public class OneOfBase : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly T3 _value3; 12 | readonly T4 _value4; 13 | readonly T5 _value5; 14 | readonly T6 _value6; 15 | readonly T7 _value7; 16 | readonly int _index; 17 | 18 | protected OneOfBase(OneOf input) 19 | { 20 | _index = input.Index; 21 | switch (_index) 22 | { 23 | case 0: _value0 = input.AsT0; break; 24 | case 1: _value1 = input.AsT1; break; 25 | case 2: _value2 = input.AsT2; break; 26 | case 3: _value3 = input.AsT3; break; 27 | case 4: _value4 = input.AsT4; break; 28 | case 5: _value5 = input.AsT5; break; 29 | case 6: _value6 = input.AsT6; break; 30 | case 7: _value7 = input.AsT7; break; 31 | default: throw new InvalidOperationException(); 32 | } 33 | } 34 | 35 | public object Value => 36 | _index switch 37 | { 38 | 0 => _value0, 39 | 1 => _value1, 40 | 2 => _value2, 41 | 3 => _value3, 42 | 4 => _value4, 43 | 5 => _value5, 44 | 6 => _value6, 45 | 7 => _value7, 46 | _ => throw new InvalidOperationException() 47 | }; 48 | 49 | public int Index => _index; 50 | 51 | public bool IsT0 => _index == 0; 52 | public bool IsT1 => _index == 1; 53 | public bool IsT2 => _index == 2; 54 | public bool IsT3 => _index == 3; 55 | public bool IsT4 => _index == 4; 56 | public bool IsT5 => _index == 5; 57 | public bool IsT6 => _index == 6; 58 | public bool IsT7 => _index == 7; 59 | 60 | public T0 AsT0 => 61 | _index == 0 ? 62 | _value0 : 63 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 64 | public T1 AsT1 => 65 | _index == 1 ? 66 | _value1 : 67 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 68 | public T2 AsT2 => 69 | _index == 2 ? 70 | _value2 : 71 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 72 | public T3 AsT3 => 73 | _index == 3 ? 74 | _value3 : 75 | throw new InvalidOperationException($"Cannot return as T3 as result is T{_index}"); 76 | public T4 AsT4 => 77 | _index == 4 ? 78 | _value4 : 79 | throw new InvalidOperationException($"Cannot return as T4 as result is T{_index}"); 80 | public T5 AsT5 => 81 | _index == 5 ? 82 | _value5 : 83 | throw new InvalidOperationException($"Cannot return as T5 as result is T{_index}"); 84 | public T6 AsT6 => 85 | _index == 6 ? 86 | _value6 : 87 | throw new InvalidOperationException($"Cannot return as T6 as result is T{_index}"); 88 | public T7 AsT7 => 89 | _index == 7 ? 90 | _value7 : 91 | throw new InvalidOperationException($"Cannot return as T7 as result is T{_index}"); 92 | 93 | 94 | 95 | public void Switch(Action f0, Action f1, Action f2, Action f3, Action f4, Action f5, Action f6, Action f7) 96 | { 97 | if (_index == 0 && f0 != null) 98 | { 99 | f0(_value0); 100 | return; 101 | } 102 | if (_index == 1 && f1 != null) 103 | { 104 | f1(_value1); 105 | return; 106 | } 107 | if (_index == 2 && f2 != null) 108 | { 109 | f2(_value2); 110 | return; 111 | } 112 | if (_index == 3 && f3 != null) 113 | { 114 | f3(_value3); 115 | return; 116 | } 117 | if (_index == 4 && f4 != null) 118 | { 119 | f4(_value4); 120 | return; 121 | } 122 | if (_index == 5 && f5 != null) 123 | { 124 | f5(_value5); 125 | return; 126 | } 127 | if (_index == 6 && f6 != null) 128 | { 129 | f6(_value6); 130 | return; 131 | } 132 | if (_index == 7 && f7 != null) 133 | { 134 | f7(_value7); 135 | return; 136 | } 137 | throw new InvalidOperationException(); 138 | } 139 | 140 | public TResult Match(Func f0, Func f1, Func f2, Func f3, Func f4, Func f5, Func f6, Func f7) 141 | { 142 | if (_index == 0 && f0 != null) 143 | { 144 | return f0(_value0); 145 | } 146 | if (_index == 1 && f1 != null) 147 | { 148 | return f1(_value1); 149 | } 150 | if (_index == 2 && f2 != null) 151 | { 152 | return f2(_value2); 153 | } 154 | if (_index == 3 && f3 != null) 155 | { 156 | return f3(_value3); 157 | } 158 | if (_index == 4 && f4 != null) 159 | { 160 | return f4(_value4); 161 | } 162 | if (_index == 5 && f5 != null) 163 | { 164 | return f5(_value5); 165 | } 166 | if (_index == 6 && f6 != null) 167 | { 168 | return f6(_value6); 169 | } 170 | if (_index == 7 && f7 != null) 171 | { 172 | return f7(_value7); 173 | } 174 | throw new InvalidOperationException(); 175 | } 176 | 177 | 178 | 179 | 180 | 181 | public bool TryPickT0(out T0 value, out OneOf remainder) 182 | { 183 | value = IsT0 ? AsT0 : default; 184 | remainder = _index switch 185 | { 186 | 0 => default, 187 | 1 => AsT1, 188 | 2 => AsT2, 189 | 3 => AsT3, 190 | 4 => AsT4, 191 | 5 => AsT5, 192 | 6 => AsT6, 193 | 7 => AsT7, 194 | _ => throw new InvalidOperationException() 195 | }; 196 | return this.IsT0; 197 | } 198 | 199 | public bool TryPickT1(out T1 value, out OneOf remainder) 200 | { 201 | value = IsT1 ? AsT1 : default; 202 | remainder = _index switch 203 | { 204 | 0 => AsT0, 205 | 1 => default, 206 | 2 => AsT2, 207 | 3 => AsT3, 208 | 4 => AsT4, 209 | 5 => AsT5, 210 | 6 => AsT6, 211 | 7 => AsT7, 212 | _ => throw new InvalidOperationException() 213 | }; 214 | return this.IsT1; 215 | } 216 | 217 | public bool TryPickT2(out T2 value, out OneOf remainder) 218 | { 219 | value = IsT2 ? AsT2 : default; 220 | remainder = _index switch 221 | { 222 | 0 => AsT0, 223 | 1 => AsT1, 224 | 2 => default, 225 | 3 => AsT3, 226 | 4 => AsT4, 227 | 5 => AsT5, 228 | 6 => AsT6, 229 | 7 => AsT7, 230 | _ => throw new InvalidOperationException() 231 | }; 232 | return this.IsT2; 233 | } 234 | 235 | public bool TryPickT3(out T3 value, out OneOf remainder) 236 | { 237 | value = IsT3 ? AsT3 : default; 238 | remainder = _index switch 239 | { 240 | 0 => AsT0, 241 | 1 => AsT1, 242 | 2 => AsT2, 243 | 3 => default, 244 | 4 => AsT4, 245 | 5 => AsT5, 246 | 6 => AsT6, 247 | 7 => AsT7, 248 | _ => throw new InvalidOperationException() 249 | }; 250 | return this.IsT3; 251 | } 252 | 253 | public bool TryPickT4(out T4 value, out OneOf remainder) 254 | { 255 | value = IsT4 ? AsT4 : default; 256 | remainder = _index switch 257 | { 258 | 0 => AsT0, 259 | 1 => AsT1, 260 | 2 => AsT2, 261 | 3 => AsT3, 262 | 4 => default, 263 | 5 => AsT5, 264 | 6 => AsT6, 265 | 7 => AsT7, 266 | _ => throw new InvalidOperationException() 267 | }; 268 | return this.IsT4; 269 | } 270 | 271 | public bool TryPickT5(out T5 value, out OneOf remainder) 272 | { 273 | value = IsT5 ? AsT5 : default; 274 | remainder = _index switch 275 | { 276 | 0 => AsT0, 277 | 1 => AsT1, 278 | 2 => AsT2, 279 | 3 => AsT3, 280 | 4 => AsT4, 281 | 5 => default, 282 | 6 => AsT6, 283 | 7 => AsT7, 284 | _ => throw new InvalidOperationException() 285 | }; 286 | return this.IsT5; 287 | } 288 | 289 | public bool TryPickT6(out T6 value, out OneOf remainder) 290 | { 291 | value = IsT6 ? AsT6 : default; 292 | remainder = _index switch 293 | { 294 | 0 => AsT0, 295 | 1 => AsT1, 296 | 2 => AsT2, 297 | 3 => AsT3, 298 | 4 => AsT4, 299 | 5 => AsT5, 300 | 6 => default, 301 | 7 => AsT7, 302 | _ => throw new InvalidOperationException() 303 | }; 304 | return this.IsT6; 305 | } 306 | 307 | public bool TryPickT7(out T7 value, out OneOf remainder) 308 | { 309 | value = IsT7 ? AsT7 : default; 310 | remainder = _index switch 311 | { 312 | 0 => AsT0, 313 | 1 => AsT1, 314 | 2 => AsT2, 315 | 3 => AsT3, 316 | 4 => AsT4, 317 | 5 => AsT5, 318 | 6 => AsT6, 319 | 7 => default, 320 | _ => throw new InvalidOperationException() 321 | }; 322 | return this.IsT7; 323 | } 324 | 325 | bool Equals(OneOfBase other) => 326 | _index == other._index && 327 | _index switch 328 | { 329 | 0 => Equals(_value0, other._value0), 330 | 1 => Equals(_value1, other._value1), 331 | 2 => Equals(_value2, other._value2), 332 | 3 => Equals(_value3, other._value3), 333 | 4 => Equals(_value4, other._value4), 334 | 5 => Equals(_value5, other._value5), 335 | 6 => Equals(_value6, other._value6), 336 | 7 => Equals(_value7, other._value7), 337 | _ => false 338 | }; 339 | 340 | public override bool Equals(object obj) 341 | { 342 | if (ReferenceEquals(null, obj)) 343 | { 344 | return false; 345 | } 346 | 347 | if (ReferenceEquals(this, obj)) { 348 | return true; 349 | } 350 | 351 | return obj is OneOfBase o && Equals(o); 352 | } 353 | 354 | public override string ToString() => 355 | _index switch { 356 | 0 => FormatValue(_value0), 357 | 1 => FormatValue(_value1), 358 | 2 => FormatValue(_value2), 359 | 3 => FormatValue(_value3), 360 | 4 => FormatValue(_value4), 361 | 5 => FormatValue(_value5), 362 | 6 => FormatValue(_value6), 363 | 7 => FormatValue(_value7), 364 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 365 | }; 366 | 367 | public override int GetHashCode() 368 | { 369 | unchecked 370 | { 371 | int hashCode = _index switch 372 | { 373 | 0 => _value0?.GetHashCode(), 374 | 1 => _value1?.GetHashCode(), 375 | 2 => _value2?.GetHashCode(), 376 | 3 => _value3?.GetHashCode(), 377 | 4 => _value4?.GetHashCode(), 378 | 5 => _value5?.GetHashCode(), 379 | 6 => _value6?.GetHashCode(), 380 | 7 => _value7?.GetHashCode(), 381 | _ => 0 382 | } ?? 0; 383 | return (hashCode*397) ^ _index; 384 | } 385 | } 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /OneOf/OneOfT0.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public readonly struct OneOf : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly int _index; 10 | 11 | OneOf(int index, T0 value0 = default) 12 | { 13 | _index = index; 14 | _value0 = value0; 15 | } 16 | 17 | public object Value => 18 | _index switch 19 | { 20 | 0 => _value0, 21 | _ => throw new InvalidOperationException() 22 | }; 23 | 24 | public int Index => _index; 25 | 26 | public bool IsT0 => _index == 0; 27 | 28 | public T0 AsT0 => 29 | _index == 0 ? 30 | _value0 : 31 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 32 | 33 | public static implicit operator OneOf(T0 t) => new OneOf(0, value0: t); 34 | 35 | public void Switch(Action f0) 36 | { 37 | if (_index == 0 && f0 != null) 38 | { 39 | f0(_value0); 40 | return; 41 | } 42 | throw new InvalidOperationException(); 43 | } 44 | 45 | public TResult Match(Func f0) 46 | { 47 | if (_index == 0 && f0 != null) 48 | { 49 | return f0(_value0); 50 | } 51 | throw new InvalidOperationException(); 52 | } 53 | 54 | public static OneOf FromT0(T0 input) => input; 55 | 56 | 57 | public OneOf MapT0(Func mapFunc) 58 | { 59 | if (mapFunc == null) 60 | { 61 | throw new ArgumentNullException(nameof(mapFunc)); 62 | } 63 | return _index switch 64 | { 65 | 0 => mapFunc(AsT0), 66 | _ => throw new InvalidOperationException() 67 | }; 68 | } 69 | 70 | bool Equals(OneOf other) => 71 | _index == other._index && 72 | _index switch 73 | { 74 | 0 => Equals(_value0, other._value0), 75 | _ => false 76 | }; 77 | 78 | public override bool Equals(object obj) 79 | { 80 | if (ReferenceEquals(null, obj)) 81 | { 82 | return false; 83 | } 84 | 85 | return obj is OneOf o && Equals(o); 86 | } 87 | 88 | public override string ToString() => 89 | _index switch { 90 | 0 => FormatValue(_value0), 91 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 92 | }; 93 | 94 | public override int GetHashCode() 95 | { 96 | unchecked 97 | { 98 | int hashCode = _index switch 99 | { 100 | 0 => _value0?.GetHashCode(), 101 | _ => 0 102 | } ?? 0; 103 | return (hashCode*397) ^ _index; 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /OneOf/OneOfT1.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public readonly struct OneOf : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly int _index; 11 | 12 | OneOf(int index, T0 value0 = default, T1 value1 = default) 13 | { 14 | _index = index; 15 | _value0 = value0; 16 | _value1 = value1; 17 | } 18 | 19 | public object Value => 20 | _index switch 21 | { 22 | 0 => _value0, 23 | 1 => _value1, 24 | _ => throw new InvalidOperationException() 25 | }; 26 | 27 | public int Index => _index; 28 | 29 | public bool IsT0 => _index == 0; 30 | public bool IsT1 => _index == 1; 31 | 32 | public T0 AsT0 => 33 | _index == 0 ? 34 | _value0 : 35 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 36 | public T1 AsT1 => 37 | _index == 1 ? 38 | _value1 : 39 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 40 | 41 | public static implicit operator OneOf(T0 t) => new OneOf(0, value0: t); 42 | public static implicit operator OneOf(T1 t) => new OneOf(1, value1: t); 43 | 44 | public void Switch(Action f0, Action f1) 45 | { 46 | if (_index == 0 && f0 != null) 47 | { 48 | f0(_value0); 49 | return; 50 | } 51 | if (_index == 1 && f1 != null) 52 | { 53 | f1(_value1); 54 | return; 55 | } 56 | throw new InvalidOperationException(); 57 | } 58 | 59 | public TResult Match(Func f0, Func f1) 60 | { 61 | if (_index == 0 && f0 != null) 62 | { 63 | return f0(_value0); 64 | } 65 | if (_index == 1 && f1 != null) 66 | { 67 | return f1(_value1); 68 | } 69 | throw new InvalidOperationException(); 70 | } 71 | 72 | public static OneOf FromT0(T0 input) => input; 73 | public static OneOf FromT1(T1 input) => input; 74 | 75 | 76 | public OneOf MapT0(Func mapFunc) 77 | { 78 | if (mapFunc == null) 79 | { 80 | throw new ArgumentNullException(nameof(mapFunc)); 81 | } 82 | return _index switch 83 | { 84 | 0 => mapFunc(AsT0), 85 | 1 => AsT1, 86 | _ => throw new InvalidOperationException() 87 | }; 88 | } 89 | 90 | public OneOf MapT1(Func mapFunc) 91 | { 92 | if (mapFunc == null) 93 | { 94 | throw new ArgumentNullException(nameof(mapFunc)); 95 | } 96 | return _index switch 97 | { 98 | 0 => AsT0, 99 | 1 => mapFunc(AsT1), 100 | _ => throw new InvalidOperationException() 101 | }; 102 | } 103 | 104 | public bool TryPickT0(out T0 value, out T1 remainder) 105 | { 106 | value = IsT0 ? AsT0 : default; 107 | remainder = _index switch 108 | { 109 | 0 => default, 110 | 1 => AsT1, 111 | _ => throw new InvalidOperationException() 112 | }; 113 | return this.IsT0; 114 | } 115 | 116 | public bool TryPickT1(out T1 value, out T0 remainder) 117 | { 118 | value = IsT1 ? AsT1 : default; 119 | remainder = _index switch 120 | { 121 | 0 => AsT0, 122 | 1 => default, 123 | _ => throw new InvalidOperationException() 124 | }; 125 | return this.IsT1; 126 | } 127 | 128 | bool Equals(OneOf other) => 129 | _index == other._index && 130 | _index switch 131 | { 132 | 0 => Equals(_value0, other._value0), 133 | 1 => Equals(_value1, other._value1), 134 | _ => false 135 | }; 136 | 137 | public override bool Equals(object obj) 138 | { 139 | if (ReferenceEquals(null, obj)) 140 | { 141 | return false; 142 | } 143 | 144 | return obj is OneOf o && Equals(o); 145 | } 146 | 147 | public override string ToString() => 148 | _index switch { 149 | 0 => FormatValue(_value0), 150 | 1 => FormatValue(_value1), 151 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 152 | }; 153 | 154 | public override int GetHashCode() 155 | { 156 | unchecked 157 | { 158 | int hashCode = _index switch 159 | { 160 | 0 => _value0?.GetHashCode(), 161 | 1 => _value1?.GetHashCode(), 162 | _ => 0 163 | } ?? 0; 164 | return (hashCode*397) ^ _index; 165 | } 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /OneOf/OneOfT2.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public readonly struct OneOf : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly int _index; 12 | 13 | OneOf(int index, T0 value0 = default, T1 value1 = default, T2 value2 = default) 14 | { 15 | _index = index; 16 | _value0 = value0; 17 | _value1 = value1; 18 | _value2 = value2; 19 | } 20 | 21 | public object Value => 22 | _index switch 23 | { 24 | 0 => _value0, 25 | 1 => _value1, 26 | 2 => _value2, 27 | _ => throw new InvalidOperationException() 28 | }; 29 | 30 | public int Index => _index; 31 | 32 | public bool IsT0 => _index == 0; 33 | public bool IsT1 => _index == 1; 34 | public bool IsT2 => _index == 2; 35 | 36 | public T0 AsT0 => 37 | _index == 0 ? 38 | _value0 : 39 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 40 | public T1 AsT1 => 41 | _index == 1 ? 42 | _value1 : 43 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 44 | public T2 AsT2 => 45 | _index == 2 ? 46 | _value2 : 47 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 48 | 49 | public static implicit operator OneOf(T0 t) => new OneOf(0, value0: t); 50 | public static implicit operator OneOf(T1 t) => new OneOf(1, value1: t); 51 | public static implicit operator OneOf(T2 t) => new OneOf(2, value2: t); 52 | 53 | public void Switch(Action f0, Action f1, Action f2) 54 | { 55 | if (_index == 0 && f0 != null) 56 | { 57 | f0(_value0); 58 | return; 59 | } 60 | if (_index == 1 && f1 != null) 61 | { 62 | f1(_value1); 63 | return; 64 | } 65 | if (_index == 2 && f2 != null) 66 | { 67 | f2(_value2); 68 | return; 69 | } 70 | throw new InvalidOperationException(); 71 | } 72 | 73 | public TResult Match(Func f0, Func f1, Func f2) 74 | { 75 | if (_index == 0 && f0 != null) 76 | { 77 | return f0(_value0); 78 | } 79 | if (_index == 1 && f1 != null) 80 | { 81 | return f1(_value1); 82 | } 83 | if (_index == 2 && f2 != null) 84 | { 85 | return f2(_value2); 86 | } 87 | throw new InvalidOperationException(); 88 | } 89 | 90 | public static OneOf FromT0(T0 input) => input; 91 | public static OneOf FromT1(T1 input) => input; 92 | public static OneOf FromT2(T2 input) => input; 93 | 94 | 95 | public OneOf MapT0(Func mapFunc) 96 | { 97 | if (mapFunc == null) 98 | { 99 | throw new ArgumentNullException(nameof(mapFunc)); 100 | } 101 | return _index switch 102 | { 103 | 0 => mapFunc(AsT0), 104 | 1 => AsT1, 105 | 2 => AsT2, 106 | _ => throw new InvalidOperationException() 107 | }; 108 | } 109 | 110 | public OneOf MapT1(Func mapFunc) 111 | { 112 | if (mapFunc == null) 113 | { 114 | throw new ArgumentNullException(nameof(mapFunc)); 115 | } 116 | return _index switch 117 | { 118 | 0 => AsT0, 119 | 1 => mapFunc(AsT1), 120 | 2 => AsT2, 121 | _ => throw new InvalidOperationException() 122 | }; 123 | } 124 | 125 | public OneOf MapT2(Func mapFunc) 126 | { 127 | if (mapFunc == null) 128 | { 129 | throw new ArgumentNullException(nameof(mapFunc)); 130 | } 131 | return _index switch 132 | { 133 | 0 => AsT0, 134 | 1 => AsT1, 135 | 2 => mapFunc(AsT2), 136 | _ => throw new InvalidOperationException() 137 | }; 138 | } 139 | 140 | public bool TryPickT0(out T0 value, out OneOf remainder) 141 | { 142 | value = IsT0 ? AsT0 : default; 143 | remainder = _index switch 144 | { 145 | 0 => default, 146 | 1 => AsT1, 147 | 2 => AsT2, 148 | _ => throw new InvalidOperationException() 149 | }; 150 | return this.IsT0; 151 | } 152 | 153 | public bool TryPickT1(out T1 value, out OneOf remainder) 154 | { 155 | value = IsT1 ? AsT1 : default; 156 | remainder = _index switch 157 | { 158 | 0 => AsT0, 159 | 1 => default, 160 | 2 => AsT2, 161 | _ => throw new InvalidOperationException() 162 | }; 163 | return this.IsT1; 164 | } 165 | 166 | public bool TryPickT2(out T2 value, out OneOf remainder) 167 | { 168 | value = IsT2 ? AsT2 : default; 169 | remainder = _index switch 170 | { 171 | 0 => AsT0, 172 | 1 => AsT1, 173 | 2 => default, 174 | _ => throw new InvalidOperationException() 175 | }; 176 | return this.IsT2; 177 | } 178 | 179 | bool Equals(OneOf other) => 180 | _index == other._index && 181 | _index switch 182 | { 183 | 0 => Equals(_value0, other._value0), 184 | 1 => Equals(_value1, other._value1), 185 | 2 => Equals(_value2, other._value2), 186 | _ => false 187 | }; 188 | 189 | public override bool Equals(object obj) 190 | { 191 | if (ReferenceEquals(null, obj)) 192 | { 193 | return false; 194 | } 195 | 196 | return obj is OneOf o && Equals(o); 197 | } 198 | 199 | public override string ToString() => 200 | _index switch { 201 | 0 => FormatValue(_value0), 202 | 1 => FormatValue(_value1), 203 | 2 => FormatValue(_value2), 204 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 205 | }; 206 | 207 | public override int GetHashCode() 208 | { 209 | unchecked 210 | { 211 | int hashCode = _index switch 212 | { 213 | 0 => _value0?.GetHashCode(), 214 | 1 => _value1?.GetHashCode(), 215 | 2 => _value2?.GetHashCode(), 216 | _ => 0 217 | } ?? 0; 218 | return (hashCode*397) ^ _index; 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /OneOf/OneOfT3.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public readonly struct OneOf : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly T3 _value3; 12 | readonly int _index; 13 | 14 | OneOf(int index, T0 value0 = default, T1 value1 = default, T2 value2 = default, T3 value3 = default) 15 | { 16 | _index = index; 17 | _value0 = value0; 18 | _value1 = value1; 19 | _value2 = value2; 20 | _value3 = value3; 21 | } 22 | 23 | public object Value => 24 | _index switch 25 | { 26 | 0 => _value0, 27 | 1 => _value1, 28 | 2 => _value2, 29 | 3 => _value3, 30 | _ => throw new InvalidOperationException() 31 | }; 32 | 33 | public int Index => _index; 34 | 35 | public bool IsT0 => _index == 0; 36 | public bool IsT1 => _index == 1; 37 | public bool IsT2 => _index == 2; 38 | public bool IsT3 => _index == 3; 39 | 40 | public T0 AsT0 => 41 | _index == 0 ? 42 | _value0 : 43 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 44 | public T1 AsT1 => 45 | _index == 1 ? 46 | _value1 : 47 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 48 | public T2 AsT2 => 49 | _index == 2 ? 50 | _value2 : 51 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 52 | public T3 AsT3 => 53 | _index == 3 ? 54 | _value3 : 55 | throw new InvalidOperationException($"Cannot return as T3 as result is T{_index}"); 56 | 57 | public static implicit operator OneOf(T0 t) => new OneOf(0, value0: t); 58 | public static implicit operator OneOf(T1 t) => new OneOf(1, value1: t); 59 | public static implicit operator OneOf(T2 t) => new OneOf(2, value2: t); 60 | public static implicit operator OneOf(T3 t) => new OneOf(3, value3: t); 61 | 62 | public void Switch(Action f0, Action f1, Action f2, Action f3) 63 | { 64 | if (_index == 0 && f0 != null) 65 | { 66 | f0(_value0); 67 | return; 68 | } 69 | if (_index == 1 && f1 != null) 70 | { 71 | f1(_value1); 72 | return; 73 | } 74 | if (_index == 2 && f2 != null) 75 | { 76 | f2(_value2); 77 | return; 78 | } 79 | if (_index == 3 && f3 != null) 80 | { 81 | f3(_value3); 82 | return; 83 | } 84 | throw new InvalidOperationException(); 85 | } 86 | 87 | public TResult Match(Func f0, Func f1, Func f2, Func f3) 88 | { 89 | if (_index == 0 && f0 != null) 90 | { 91 | return f0(_value0); 92 | } 93 | if (_index == 1 && f1 != null) 94 | { 95 | return f1(_value1); 96 | } 97 | if (_index == 2 && f2 != null) 98 | { 99 | return f2(_value2); 100 | } 101 | if (_index == 3 && f3 != null) 102 | { 103 | return f3(_value3); 104 | } 105 | throw new InvalidOperationException(); 106 | } 107 | 108 | public static OneOf FromT0(T0 input) => input; 109 | public static OneOf FromT1(T1 input) => input; 110 | public static OneOf FromT2(T2 input) => input; 111 | public static OneOf FromT3(T3 input) => input; 112 | 113 | 114 | public OneOf MapT0(Func mapFunc) 115 | { 116 | if (mapFunc == null) 117 | { 118 | throw new ArgumentNullException(nameof(mapFunc)); 119 | } 120 | return _index switch 121 | { 122 | 0 => mapFunc(AsT0), 123 | 1 => AsT1, 124 | 2 => AsT2, 125 | 3 => AsT3, 126 | _ => throw new InvalidOperationException() 127 | }; 128 | } 129 | 130 | public OneOf MapT1(Func mapFunc) 131 | { 132 | if (mapFunc == null) 133 | { 134 | throw new ArgumentNullException(nameof(mapFunc)); 135 | } 136 | return _index switch 137 | { 138 | 0 => AsT0, 139 | 1 => mapFunc(AsT1), 140 | 2 => AsT2, 141 | 3 => AsT3, 142 | _ => throw new InvalidOperationException() 143 | }; 144 | } 145 | 146 | public OneOf MapT2(Func mapFunc) 147 | { 148 | if (mapFunc == null) 149 | { 150 | throw new ArgumentNullException(nameof(mapFunc)); 151 | } 152 | return _index switch 153 | { 154 | 0 => AsT0, 155 | 1 => AsT1, 156 | 2 => mapFunc(AsT2), 157 | 3 => AsT3, 158 | _ => throw new InvalidOperationException() 159 | }; 160 | } 161 | 162 | public OneOf MapT3(Func mapFunc) 163 | { 164 | if (mapFunc == null) 165 | { 166 | throw new ArgumentNullException(nameof(mapFunc)); 167 | } 168 | return _index switch 169 | { 170 | 0 => AsT0, 171 | 1 => AsT1, 172 | 2 => AsT2, 173 | 3 => mapFunc(AsT3), 174 | _ => throw new InvalidOperationException() 175 | }; 176 | } 177 | 178 | public bool TryPickT0(out T0 value, out OneOf remainder) 179 | { 180 | value = IsT0 ? AsT0 : default; 181 | remainder = _index switch 182 | { 183 | 0 => default, 184 | 1 => AsT1, 185 | 2 => AsT2, 186 | 3 => AsT3, 187 | _ => throw new InvalidOperationException() 188 | }; 189 | return this.IsT0; 190 | } 191 | 192 | public bool TryPickT1(out T1 value, out OneOf remainder) 193 | { 194 | value = IsT1 ? AsT1 : default; 195 | remainder = _index switch 196 | { 197 | 0 => AsT0, 198 | 1 => default, 199 | 2 => AsT2, 200 | 3 => AsT3, 201 | _ => throw new InvalidOperationException() 202 | }; 203 | return this.IsT1; 204 | } 205 | 206 | public bool TryPickT2(out T2 value, out OneOf remainder) 207 | { 208 | value = IsT2 ? AsT2 : default; 209 | remainder = _index switch 210 | { 211 | 0 => AsT0, 212 | 1 => AsT1, 213 | 2 => default, 214 | 3 => AsT3, 215 | _ => throw new InvalidOperationException() 216 | }; 217 | return this.IsT2; 218 | } 219 | 220 | public bool TryPickT3(out T3 value, out OneOf remainder) 221 | { 222 | value = IsT3 ? AsT3 : default; 223 | remainder = _index switch 224 | { 225 | 0 => AsT0, 226 | 1 => AsT1, 227 | 2 => AsT2, 228 | 3 => default, 229 | _ => throw new InvalidOperationException() 230 | }; 231 | return this.IsT3; 232 | } 233 | 234 | bool Equals(OneOf other) => 235 | _index == other._index && 236 | _index switch 237 | { 238 | 0 => Equals(_value0, other._value0), 239 | 1 => Equals(_value1, other._value1), 240 | 2 => Equals(_value2, other._value2), 241 | 3 => Equals(_value3, other._value3), 242 | _ => false 243 | }; 244 | 245 | public override bool Equals(object obj) 246 | { 247 | if (ReferenceEquals(null, obj)) 248 | { 249 | return false; 250 | } 251 | 252 | return obj is OneOf o && Equals(o); 253 | } 254 | 255 | public override string ToString() => 256 | _index switch { 257 | 0 => FormatValue(_value0), 258 | 1 => FormatValue(_value1), 259 | 2 => FormatValue(_value2), 260 | 3 => FormatValue(_value3), 261 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 262 | }; 263 | 264 | public override int GetHashCode() 265 | { 266 | unchecked 267 | { 268 | int hashCode = _index switch 269 | { 270 | 0 => _value0?.GetHashCode(), 271 | 1 => _value1?.GetHashCode(), 272 | 2 => _value2?.GetHashCode(), 273 | 3 => _value3?.GetHashCode(), 274 | _ => 0 275 | } ?? 0; 276 | return (hashCode*397) ^ _index; 277 | } 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /OneOf/OneOfT4.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public readonly struct OneOf : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly T3 _value3; 12 | readonly T4 _value4; 13 | readonly int _index; 14 | 15 | OneOf(int index, T0 value0 = default, T1 value1 = default, T2 value2 = default, T3 value3 = default, T4 value4 = default) 16 | { 17 | _index = index; 18 | _value0 = value0; 19 | _value1 = value1; 20 | _value2 = value2; 21 | _value3 = value3; 22 | _value4 = value4; 23 | } 24 | 25 | public object Value => 26 | _index switch 27 | { 28 | 0 => _value0, 29 | 1 => _value1, 30 | 2 => _value2, 31 | 3 => _value3, 32 | 4 => _value4, 33 | _ => throw new InvalidOperationException() 34 | }; 35 | 36 | public int Index => _index; 37 | 38 | public bool IsT0 => _index == 0; 39 | public bool IsT1 => _index == 1; 40 | public bool IsT2 => _index == 2; 41 | public bool IsT3 => _index == 3; 42 | public bool IsT4 => _index == 4; 43 | 44 | public T0 AsT0 => 45 | _index == 0 ? 46 | _value0 : 47 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 48 | public T1 AsT1 => 49 | _index == 1 ? 50 | _value1 : 51 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 52 | public T2 AsT2 => 53 | _index == 2 ? 54 | _value2 : 55 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 56 | public T3 AsT3 => 57 | _index == 3 ? 58 | _value3 : 59 | throw new InvalidOperationException($"Cannot return as T3 as result is T{_index}"); 60 | public T4 AsT4 => 61 | _index == 4 ? 62 | _value4 : 63 | throw new InvalidOperationException($"Cannot return as T4 as result is T{_index}"); 64 | 65 | public static implicit operator OneOf(T0 t) => new OneOf(0, value0: t); 66 | public static implicit operator OneOf(T1 t) => new OneOf(1, value1: t); 67 | public static implicit operator OneOf(T2 t) => new OneOf(2, value2: t); 68 | public static implicit operator OneOf(T3 t) => new OneOf(3, value3: t); 69 | public static implicit operator OneOf(T4 t) => new OneOf(4, value4: t); 70 | 71 | public void Switch(Action f0, Action f1, Action f2, Action f3, Action f4) 72 | { 73 | if (_index == 0 && f0 != null) 74 | { 75 | f0(_value0); 76 | return; 77 | } 78 | if (_index == 1 && f1 != null) 79 | { 80 | f1(_value1); 81 | return; 82 | } 83 | if (_index == 2 && f2 != null) 84 | { 85 | f2(_value2); 86 | return; 87 | } 88 | if (_index == 3 && f3 != null) 89 | { 90 | f3(_value3); 91 | return; 92 | } 93 | if (_index == 4 && f4 != null) 94 | { 95 | f4(_value4); 96 | return; 97 | } 98 | throw new InvalidOperationException(); 99 | } 100 | 101 | public TResult Match(Func f0, Func f1, Func f2, Func f3, Func f4) 102 | { 103 | if (_index == 0 && f0 != null) 104 | { 105 | return f0(_value0); 106 | } 107 | if (_index == 1 && f1 != null) 108 | { 109 | return f1(_value1); 110 | } 111 | if (_index == 2 && f2 != null) 112 | { 113 | return f2(_value2); 114 | } 115 | if (_index == 3 && f3 != null) 116 | { 117 | return f3(_value3); 118 | } 119 | if (_index == 4 && f4 != null) 120 | { 121 | return f4(_value4); 122 | } 123 | throw new InvalidOperationException(); 124 | } 125 | 126 | public static OneOf FromT0(T0 input) => input; 127 | public static OneOf FromT1(T1 input) => input; 128 | public static OneOf FromT2(T2 input) => input; 129 | public static OneOf FromT3(T3 input) => input; 130 | public static OneOf FromT4(T4 input) => input; 131 | 132 | 133 | public OneOf MapT0(Func mapFunc) 134 | { 135 | if (mapFunc == null) 136 | { 137 | throw new ArgumentNullException(nameof(mapFunc)); 138 | } 139 | return _index switch 140 | { 141 | 0 => mapFunc(AsT0), 142 | 1 => AsT1, 143 | 2 => AsT2, 144 | 3 => AsT3, 145 | 4 => AsT4, 146 | _ => throw new InvalidOperationException() 147 | }; 148 | } 149 | 150 | public OneOf MapT1(Func mapFunc) 151 | { 152 | if (mapFunc == null) 153 | { 154 | throw new ArgumentNullException(nameof(mapFunc)); 155 | } 156 | return _index switch 157 | { 158 | 0 => AsT0, 159 | 1 => mapFunc(AsT1), 160 | 2 => AsT2, 161 | 3 => AsT3, 162 | 4 => AsT4, 163 | _ => throw new InvalidOperationException() 164 | }; 165 | } 166 | 167 | public OneOf MapT2(Func mapFunc) 168 | { 169 | if (mapFunc == null) 170 | { 171 | throw new ArgumentNullException(nameof(mapFunc)); 172 | } 173 | return _index switch 174 | { 175 | 0 => AsT0, 176 | 1 => AsT1, 177 | 2 => mapFunc(AsT2), 178 | 3 => AsT3, 179 | 4 => AsT4, 180 | _ => throw new InvalidOperationException() 181 | }; 182 | } 183 | 184 | public OneOf MapT3(Func mapFunc) 185 | { 186 | if (mapFunc == null) 187 | { 188 | throw new ArgumentNullException(nameof(mapFunc)); 189 | } 190 | return _index switch 191 | { 192 | 0 => AsT0, 193 | 1 => AsT1, 194 | 2 => AsT2, 195 | 3 => mapFunc(AsT3), 196 | 4 => AsT4, 197 | _ => throw new InvalidOperationException() 198 | }; 199 | } 200 | 201 | public OneOf MapT4(Func mapFunc) 202 | { 203 | if (mapFunc == null) 204 | { 205 | throw new ArgumentNullException(nameof(mapFunc)); 206 | } 207 | return _index switch 208 | { 209 | 0 => AsT0, 210 | 1 => AsT1, 211 | 2 => AsT2, 212 | 3 => AsT3, 213 | 4 => mapFunc(AsT4), 214 | _ => throw new InvalidOperationException() 215 | }; 216 | } 217 | 218 | public bool TryPickT0(out T0 value, out OneOf remainder) 219 | { 220 | value = IsT0 ? AsT0 : default; 221 | remainder = _index switch 222 | { 223 | 0 => default, 224 | 1 => AsT1, 225 | 2 => AsT2, 226 | 3 => AsT3, 227 | 4 => AsT4, 228 | _ => throw new InvalidOperationException() 229 | }; 230 | return this.IsT0; 231 | } 232 | 233 | public bool TryPickT1(out T1 value, out OneOf remainder) 234 | { 235 | value = IsT1 ? AsT1 : default; 236 | remainder = _index switch 237 | { 238 | 0 => AsT0, 239 | 1 => default, 240 | 2 => AsT2, 241 | 3 => AsT3, 242 | 4 => AsT4, 243 | _ => throw new InvalidOperationException() 244 | }; 245 | return this.IsT1; 246 | } 247 | 248 | public bool TryPickT2(out T2 value, out OneOf remainder) 249 | { 250 | value = IsT2 ? AsT2 : default; 251 | remainder = _index switch 252 | { 253 | 0 => AsT0, 254 | 1 => AsT1, 255 | 2 => default, 256 | 3 => AsT3, 257 | 4 => AsT4, 258 | _ => throw new InvalidOperationException() 259 | }; 260 | return this.IsT2; 261 | } 262 | 263 | public bool TryPickT3(out T3 value, out OneOf remainder) 264 | { 265 | value = IsT3 ? AsT3 : default; 266 | remainder = _index switch 267 | { 268 | 0 => AsT0, 269 | 1 => AsT1, 270 | 2 => AsT2, 271 | 3 => default, 272 | 4 => AsT4, 273 | _ => throw new InvalidOperationException() 274 | }; 275 | return this.IsT3; 276 | } 277 | 278 | public bool TryPickT4(out T4 value, out OneOf remainder) 279 | { 280 | value = IsT4 ? AsT4 : default; 281 | remainder = _index switch 282 | { 283 | 0 => AsT0, 284 | 1 => AsT1, 285 | 2 => AsT2, 286 | 3 => AsT3, 287 | 4 => default, 288 | _ => throw new InvalidOperationException() 289 | }; 290 | return this.IsT4; 291 | } 292 | 293 | bool Equals(OneOf other) => 294 | _index == other._index && 295 | _index switch 296 | { 297 | 0 => Equals(_value0, other._value0), 298 | 1 => Equals(_value1, other._value1), 299 | 2 => Equals(_value2, other._value2), 300 | 3 => Equals(_value3, other._value3), 301 | 4 => Equals(_value4, other._value4), 302 | _ => false 303 | }; 304 | 305 | public override bool Equals(object obj) 306 | { 307 | if (ReferenceEquals(null, obj)) 308 | { 309 | return false; 310 | } 311 | 312 | return obj is OneOf o && Equals(o); 313 | } 314 | 315 | public override string ToString() => 316 | _index switch { 317 | 0 => FormatValue(_value0), 318 | 1 => FormatValue(_value1), 319 | 2 => FormatValue(_value2), 320 | 3 => FormatValue(_value3), 321 | 4 => FormatValue(_value4), 322 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 323 | }; 324 | 325 | public override int GetHashCode() 326 | { 327 | unchecked 328 | { 329 | int hashCode = _index switch 330 | { 331 | 0 => _value0?.GetHashCode(), 332 | 1 => _value1?.GetHashCode(), 333 | 2 => _value2?.GetHashCode(), 334 | 3 => _value3?.GetHashCode(), 335 | 4 => _value4?.GetHashCode(), 336 | _ => 0 337 | } ?? 0; 338 | return (hashCode*397) ^ _index; 339 | } 340 | } 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /OneOf/OneOfT5.generated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static OneOf.Functions; 3 | 4 | namespace OneOf 5 | { 6 | public readonly struct OneOf : IOneOf 7 | { 8 | readonly T0 _value0; 9 | readonly T1 _value1; 10 | readonly T2 _value2; 11 | readonly T3 _value3; 12 | readonly T4 _value4; 13 | readonly T5 _value5; 14 | readonly int _index; 15 | 16 | OneOf(int index, T0 value0 = default, T1 value1 = default, T2 value2 = default, T3 value3 = default, T4 value4 = default, T5 value5 = default) 17 | { 18 | _index = index; 19 | _value0 = value0; 20 | _value1 = value1; 21 | _value2 = value2; 22 | _value3 = value3; 23 | _value4 = value4; 24 | _value5 = value5; 25 | } 26 | 27 | public object Value => 28 | _index switch 29 | { 30 | 0 => _value0, 31 | 1 => _value1, 32 | 2 => _value2, 33 | 3 => _value3, 34 | 4 => _value4, 35 | 5 => _value5, 36 | _ => throw new InvalidOperationException() 37 | }; 38 | 39 | public int Index => _index; 40 | 41 | public bool IsT0 => _index == 0; 42 | public bool IsT1 => _index == 1; 43 | public bool IsT2 => _index == 2; 44 | public bool IsT3 => _index == 3; 45 | public bool IsT4 => _index == 4; 46 | public bool IsT5 => _index == 5; 47 | 48 | public T0 AsT0 => 49 | _index == 0 ? 50 | _value0 : 51 | throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 52 | public T1 AsT1 => 53 | _index == 1 ? 54 | _value1 : 55 | throw new InvalidOperationException($"Cannot return as T1 as result is T{_index}"); 56 | public T2 AsT2 => 57 | _index == 2 ? 58 | _value2 : 59 | throw new InvalidOperationException($"Cannot return as T2 as result is T{_index}"); 60 | public T3 AsT3 => 61 | _index == 3 ? 62 | _value3 : 63 | throw new InvalidOperationException($"Cannot return as T3 as result is T{_index}"); 64 | public T4 AsT4 => 65 | _index == 4 ? 66 | _value4 : 67 | throw new InvalidOperationException($"Cannot return as T4 as result is T{_index}"); 68 | public T5 AsT5 => 69 | _index == 5 ? 70 | _value5 : 71 | throw new InvalidOperationException($"Cannot return as T5 as result is T{_index}"); 72 | 73 | public static implicit operator OneOf(T0 t) => new OneOf(0, value0: t); 74 | public static implicit operator OneOf(T1 t) => new OneOf(1, value1: t); 75 | public static implicit operator OneOf(T2 t) => new OneOf(2, value2: t); 76 | public static implicit operator OneOf(T3 t) => new OneOf(3, value3: t); 77 | public static implicit operator OneOf(T4 t) => new OneOf(4, value4: t); 78 | public static implicit operator OneOf(T5 t) => new OneOf(5, value5: t); 79 | 80 | public void Switch(Action f0, Action f1, Action f2, Action f3, Action f4, Action f5) 81 | { 82 | if (_index == 0 && f0 != null) 83 | { 84 | f0(_value0); 85 | return; 86 | } 87 | if (_index == 1 && f1 != null) 88 | { 89 | f1(_value1); 90 | return; 91 | } 92 | if (_index == 2 && f2 != null) 93 | { 94 | f2(_value2); 95 | return; 96 | } 97 | if (_index == 3 && f3 != null) 98 | { 99 | f3(_value3); 100 | return; 101 | } 102 | if (_index == 4 && f4 != null) 103 | { 104 | f4(_value4); 105 | return; 106 | } 107 | if (_index == 5 && f5 != null) 108 | { 109 | f5(_value5); 110 | return; 111 | } 112 | throw new InvalidOperationException(); 113 | } 114 | 115 | public TResult Match(Func f0, Func f1, Func f2, Func f3, Func f4, Func f5) 116 | { 117 | if (_index == 0 && f0 != null) 118 | { 119 | return f0(_value0); 120 | } 121 | if (_index == 1 && f1 != null) 122 | { 123 | return f1(_value1); 124 | } 125 | if (_index == 2 && f2 != null) 126 | { 127 | return f2(_value2); 128 | } 129 | if (_index == 3 && f3 != null) 130 | { 131 | return f3(_value3); 132 | } 133 | if (_index == 4 && f4 != null) 134 | { 135 | return f4(_value4); 136 | } 137 | if (_index == 5 && f5 != null) 138 | { 139 | return f5(_value5); 140 | } 141 | throw new InvalidOperationException(); 142 | } 143 | 144 | public static OneOf FromT0(T0 input) => input; 145 | public static OneOf FromT1(T1 input) => input; 146 | public static OneOf FromT2(T2 input) => input; 147 | public static OneOf FromT3(T3 input) => input; 148 | public static OneOf FromT4(T4 input) => input; 149 | public static OneOf FromT5(T5 input) => input; 150 | 151 | 152 | public OneOf MapT0(Func mapFunc) 153 | { 154 | if (mapFunc == null) 155 | { 156 | throw new ArgumentNullException(nameof(mapFunc)); 157 | } 158 | return _index switch 159 | { 160 | 0 => mapFunc(AsT0), 161 | 1 => AsT1, 162 | 2 => AsT2, 163 | 3 => AsT3, 164 | 4 => AsT4, 165 | 5 => AsT5, 166 | _ => throw new InvalidOperationException() 167 | }; 168 | } 169 | 170 | public OneOf MapT1(Func mapFunc) 171 | { 172 | if (mapFunc == null) 173 | { 174 | throw new ArgumentNullException(nameof(mapFunc)); 175 | } 176 | return _index switch 177 | { 178 | 0 => AsT0, 179 | 1 => mapFunc(AsT1), 180 | 2 => AsT2, 181 | 3 => AsT3, 182 | 4 => AsT4, 183 | 5 => AsT5, 184 | _ => throw new InvalidOperationException() 185 | }; 186 | } 187 | 188 | public OneOf MapT2(Func mapFunc) 189 | { 190 | if (mapFunc == null) 191 | { 192 | throw new ArgumentNullException(nameof(mapFunc)); 193 | } 194 | return _index switch 195 | { 196 | 0 => AsT0, 197 | 1 => AsT1, 198 | 2 => mapFunc(AsT2), 199 | 3 => AsT3, 200 | 4 => AsT4, 201 | 5 => AsT5, 202 | _ => throw new InvalidOperationException() 203 | }; 204 | } 205 | 206 | public OneOf MapT3(Func mapFunc) 207 | { 208 | if (mapFunc == null) 209 | { 210 | throw new ArgumentNullException(nameof(mapFunc)); 211 | } 212 | return _index switch 213 | { 214 | 0 => AsT0, 215 | 1 => AsT1, 216 | 2 => AsT2, 217 | 3 => mapFunc(AsT3), 218 | 4 => AsT4, 219 | 5 => AsT5, 220 | _ => throw new InvalidOperationException() 221 | }; 222 | } 223 | 224 | public OneOf MapT4(Func mapFunc) 225 | { 226 | if (mapFunc == null) 227 | { 228 | throw new ArgumentNullException(nameof(mapFunc)); 229 | } 230 | return _index switch 231 | { 232 | 0 => AsT0, 233 | 1 => AsT1, 234 | 2 => AsT2, 235 | 3 => AsT3, 236 | 4 => mapFunc(AsT4), 237 | 5 => AsT5, 238 | _ => throw new InvalidOperationException() 239 | }; 240 | } 241 | 242 | public OneOf MapT5(Func mapFunc) 243 | { 244 | if (mapFunc == null) 245 | { 246 | throw new ArgumentNullException(nameof(mapFunc)); 247 | } 248 | return _index switch 249 | { 250 | 0 => AsT0, 251 | 1 => AsT1, 252 | 2 => AsT2, 253 | 3 => AsT3, 254 | 4 => AsT4, 255 | 5 => mapFunc(AsT5), 256 | _ => throw new InvalidOperationException() 257 | }; 258 | } 259 | 260 | public bool TryPickT0(out T0 value, out OneOf remainder) 261 | { 262 | value = IsT0 ? AsT0 : default; 263 | remainder = _index switch 264 | { 265 | 0 => default, 266 | 1 => AsT1, 267 | 2 => AsT2, 268 | 3 => AsT3, 269 | 4 => AsT4, 270 | 5 => AsT5, 271 | _ => throw new InvalidOperationException() 272 | }; 273 | return this.IsT0; 274 | } 275 | 276 | public bool TryPickT1(out T1 value, out OneOf remainder) 277 | { 278 | value = IsT1 ? AsT1 : default; 279 | remainder = _index switch 280 | { 281 | 0 => AsT0, 282 | 1 => default, 283 | 2 => AsT2, 284 | 3 => AsT3, 285 | 4 => AsT4, 286 | 5 => AsT5, 287 | _ => throw new InvalidOperationException() 288 | }; 289 | return this.IsT1; 290 | } 291 | 292 | public bool TryPickT2(out T2 value, out OneOf remainder) 293 | { 294 | value = IsT2 ? AsT2 : default; 295 | remainder = _index switch 296 | { 297 | 0 => AsT0, 298 | 1 => AsT1, 299 | 2 => default, 300 | 3 => AsT3, 301 | 4 => AsT4, 302 | 5 => AsT5, 303 | _ => throw new InvalidOperationException() 304 | }; 305 | return this.IsT2; 306 | } 307 | 308 | public bool TryPickT3(out T3 value, out OneOf remainder) 309 | { 310 | value = IsT3 ? AsT3 : default; 311 | remainder = _index switch 312 | { 313 | 0 => AsT0, 314 | 1 => AsT1, 315 | 2 => AsT2, 316 | 3 => default, 317 | 4 => AsT4, 318 | 5 => AsT5, 319 | _ => throw new InvalidOperationException() 320 | }; 321 | return this.IsT3; 322 | } 323 | 324 | public bool TryPickT4(out T4 value, out OneOf remainder) 325 | { 326 | value = IsT4 ? AsT4 : default; 327 | remainder = _index switch 328 | { 329 | 0 => AsT0, 330 | 1 => AsT1, 331 | 2 => AsT2, 332 | 3 => AsT3, 333 | 4 => default, 334 | 5 => AsT5, 335 | _ => throw new InvalidOperationException() 336 | }; 337 | return this.IsT4; 338 | } 339 | 340 | public bool TryPickT5(out T5 value, out OneOf remainder) 341 | { 342 | value = IsT5 ? AsT5 : default; 343 | remainder = _index switch 344 | { 345 | 0 => AsT0, 346 | 1 => AsT1, 347 | 2 => AsT2, 348 | 3 => AsT3, 349 | 4 => AsT4, 350 | 5 => default, 351 | _ => throw new InvalidOperationException() 352 | }; 353 | return this.IsT5; 354 | } 355 | 356 | bool Equals(OneOf other) => 357 | _index == other._index && 358 | _index switch 359 | { 360 | 0 => Equals(_value0, other._value0), 361 | 1 => Equals(_value1, other._value1), 362 | 2 => Equals(_value2, other._value2), 363 | 3 => Equals(_value3, other._value3), 364 | 4 => Equals(_value4, other._value4), 365 | 5 => Equals(_value5, other._value5), 366 | _ => false 367 | }; 368 | 369 | public override bool Equals(object obj) 370 | { 371 | if (ReferenceEquals(null, obj)) 372 | { 373 | return false; 374 | } 375 | 376 | return obj is OneOf o && Equals(o); 377 | } 378 | 379 | public override string ToString() => 380 | _index switch { 381 | 0 => FormatValue(_value0), 382 | 1 => FormatValue(_value1), 383 | 2 => FormatValue(_value2), 384 | 3 => FormatValue(_value3), 385 | 4 => FormatValue(_value4), 386 | 5 => FormatValue(_value5), 387 | _ => throw new InvalidOperationException("Unexpected index, which indicates a problem in the OneOf codegen.") 388 | }; 389 | 390 | public override int GetHashCode() 391 | { 392 | unchecked 393 | { 394 | int hashCode = _index switch 395 | { 396 | 0 => _value0?.GetHashCode(), 397 | 1 => _value1?.GetHashCode(), 398 | 2 => _value2?.GetHashCode(), 399 | 3 => _value3?.GetHashCode(), 400 | 4 => _value4?.GetHashCode(), 401 | 5 => _value5?.GetHashCode(), 402 | _ => 0 403 | } ?? 0; 404 | return (hashCode*397) ^ _index; 405 | } 406 | } 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /OneOf/Types/Assorted.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | namespace OneOf.Types 5 | { 6 | public struct Yes { } 7 | public struct No { } 8 | public struct Maybe { } 9 | 10 | public struct Unknown { } 11 | public struct True { } 12 | public struct False { } 13 | 14 | public struct All { } 15 | public struct Some { } 16 | 17 | public struct None 18 | { 19 | public static OneOf Of(T t) => new None(); 20 | } 21 | 22 | public struct NotFound { } 23 | 24 | public struct Success { } 25 | 26 | public struct Success 27 | { 28 | public Success(T value) 29 | { 30 | Value = value; 31 | } 32 | public T Value { get; } 33 | } 34 | 35 | public struct Result 36 | { 37 | public Result(T value) 38 | { 39 | Value = value; 40 | } 41 | public T Value { get; } 42 | } 43 | 44 | public struct Error { } 45 | public struct Error 46 | { 47 | public Error(T value) 48 | { 49 | Value = value; 50 | } 51 | public T Value { get; } 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /OneOf/Types/TrueFalseOrNull.cs: -------------------------------------------------------------------------------- 1 | namespace OneOf.Types 2 | { 3 | public class TrueFalseOrNull : OneOf.OneOfBase 4 | { 5 | TrueFalseOrNull(OneOf _) : base(_) { } 6 | public class True { } 7 | public class False { } 8 | public class Null { } 9 | 10 | public static implicit operator TrueFalseOrNull(True _) => new TrueFalseOrNull(_); 11 | public static implicit operator TrueFalseOrNull(False _) => new TrueFalseOrNull(_); 12 | public static implicit operator TrueFalseOrNull(Null _) => new TrueFalseOrNull(_); 13 | 14 | public static implicit operator TrueFalseOrNull(bool? value) => new TrueFalseOrNull( 15 | value is null ? new Null() : 16 | value.Value ? new True() : 17 | (OneOf)new False() 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /OneOf/Types/TrueOrFalse.cs: -------------------------------------------------------------------------------- 1 | namespace OneOf.Types 2 | { 3 | public class TrueOrFalse : OneOfBase 4 | { 5 | TrueOrFalse(OneOf _) : base(_) { } 6 | public class True { } 7 | public class False { } 8 | 9 | public static implicit operator TrueOrFalse(True _) => new TrueOrFalse(_); 10 | public static implicit operator TrueOrFalse(False _) => new TrueOrFalse(_); 11 | 12 | public static implicit operator TrueOrFalse(bool value) => new TrueOrFalse( 13 | value ? new True() : 14 | (OneOf)new False() 15 | ); 16 | } 17 | } -------------------------------------------------------------------------------- /OneOf/Types/YesNoOrMaybe.cs: -------------------------------------------------------------------------------- 1 | namespace OneOf.Types 2 | { 3 | public class YesNoOrMaybe : OneOfBase 4 | { 5 | YesNoOrMaybe(OneOf _) : base(_) { } 6 | public class Yes { } 7 | public class No { } 8 | public class Maybe { } 9 | 10 | public static implicit operator YesNoOrMaybe(Yes _) => new YesNoOrMaybe(_); 11 | public static implicit operator YesNoOrMaybe(No _) => new YesNoOrMaybe(_); 12 | public static implicit operator YesNoOrMaybe(Maybe _) => new YesNoOrMaybe(_); 13 | 14 | public static implicit operator YesNoOrMaybe(bool? value) => new YesNoOrMaybe( 15 | value is null ? new Maybe() : 16 | value.Value ? new Yes() : 17 | (OneOf)new No() 18 | ); 19 | } 20 | } -------------------------------------------------------------------------------- /OneOf/Types/YesOrNo.cs: -------------------------------------------------------------------------------- 1 | namespace OneOf.Types 2 | { 3 | public class YesOrNo : OneOfBase 4 | { 5 | YesOrNo(OneOf _) : base(_) { } 6 | public class Yes { } 7 | public class No { } 8 | 9 | public static implicit operator YesOrNo(Yes _) => new YesOrNo(_); 10 | public static implicit operator YesOrNo(No _) => new YesOrNo(_); 11 | 12 | public static implicit operator YesOrNo(bool value) => new YesOrNo( 13 | value ? new Yes() : 14 | (OneOf)new No() 15 | ); 16 | } 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OneOf [![NuGet](https://img.shields.io/nuget/v/OneOf?logo=nuget)](https://www.nuget.org/packages/OneOf/) [![GitHub](https://img.shields.io/github/license/mcintyre321/OneOf)](licence.md) 2 | 3 | > "Ah! It's like a compile time checked switch statement!" - Mike Giorgaras 4 | 5 | ## Getting Started 6 | 7 | > `install-package OneOf` 8 | 9 | This library provides F# style ~discriminated~ unions for C#, using a custom type `OneOf`. An instance of this type holds a single value, which is one of the types in its generic argument list. 10 | 11 | I can't encourage you enough to give it a try! Due to exhaustive matching DUs provide an alternative to polymorphism when you want to have a method with guaranteed behaviour-per-type (i.e. adding an abstract method on a base type, and then implementing that method in each type). It's a really powerful tool, ask any f#/Scala dev! :) 12 | 13 | PS If you like OneOf, you might want to check out [ValueOf](https://github.com/mcintyre321/valueof), for one-line Value Object Type definitions. 14 | 15 | ## Use cases 16 | 17 | ### As a method return value 18 | 19 | The most frequent use case is as a return value, when you need to return different results from a method. Here's how you might use it in an MVC controller action: 20 | 21 | ```csharp 22 | public OneOf CreateUser(string username) 23 | { 24 | if (!IsValid(username)) return new InvalidName(); 25 | var user = _repo.FindByUsername(username); 26 | if(user != null) return new NameTaken(); 27 | var user = new User(username); 28 | _repo.Save(user); 29 | return user; 30 | } 31 | 32 | [HttpPost] 33 | public IActionResult Register(string username) 34 | { 35 | OneOf createUserResult = CreateUser(username); 36 | return createUserResult.Match( 37 | user => new RedirectResult("/dashboard"), 38 | invalidName => { 39 | ModelState.AddModelError(nameof(username), $"Sorry, that is not a valid username."); 40 | return View("Register"); 41 | }, 42 | nameTaken => { 43 | ModelState.AddModelError(nameof(username), "Sorry, that name is already in use."); 44 | return View("Register"); 45 | } 46 | ); 47 | } 48 | ``` 49 | 50 | #### As an 'Option' Type 51 | 52 | It's simple to use OneOf as an `Option` type - just declare a `OneOf`. OneOf comes with a variety of useful Types in the `OneOf.Types` namespace, including `Yes`, `No`, `Maybe`, `Unknown`, `True`, `False`, `All`, `Some`, and `None`. 53 | 54 | #### Benefits 55 | 56 | - True strongly typed method signature 57 | - No need to return a custom result base type e.g `IActionResult`, or even worse, a non-descriptive type (e.g. object) 58 | - The method signature accurately describes all the potential outcomes, making it easier for consumers to understand the code 59 | - Method consumer HAS to handle all cases (see 'Matching', below) 60 | - You can avoid using ["Exceptions for control flow"](http://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why) antipattern by returning custom Typed error objects 61 | 62 | ### As a method parameter value 63 | 64 | You can use also use `OneOf` as a parameter type, allowing a caller to pass different types without requiring additional overloads. This might not seem that useful for a single parameter, but if you have multiple parameters, the number of overloads required increases rapidly. 65 | 66 | ```csharp 67 | public void SetBackground(OneOf backgroundColor) { ... } 68 | 69 | //The method above can be called with either a string, a ColorName enum value or a Color instance. 70 | ``` 71 | 72 | ## Matching 73 | 74 | You use the `TOut Match(Func f0, ... Func fn)` method to get a value out. Note how the number of handlers matches the number of generic arguments. 75 | 76 | ### Advantages over `switch` or `if` or `exception` based control flow: 77 | 78 | This has a major advantage over a switch statement, as it 79 | 80 | - requires every parameter to be handled 81 | - No fallback - if you add another generic parameter, you HAVE to update all the calling code to handle your changes. 82 | 83 | In brown-field code-bases this is incredibly useful, as the default handler is often a runtime `throw NotImplementedException`, or behaviour that wouldn't suit the new result type. 84 | 85 | E.g. 86 | 87 | ```csharp 88 | OneOf backgroundColor = ...; 89 | Color c = backgroundColor.Match( 90 | str => CssHelper.GetColorFromString(str), 91 | name => new Color(name), 92 | col => col 93 | ); 94 | _window.BackgroundColor = c; 95 | ``` 96 | 97 | There is also a .Switch method, for when you aren't returning a value: 98 | 99 | ```csharp 100 | OneOf dateValue = ...; 101 | dateValue.Switch( 102 | str => AddEntry(DateTime.Parse(str), foo), 103 | int => AddEntry(int, foo) 104 | ); 105 | ``` 106 | 107 | ### TryPick𝑥 method 108 | 109 | As an alternative to `.Switch` or `.Match` you can use the `.TryPick𝑥` methods. 110 | 111 | ```csharp 112 | //TryPick𝑥 methods for OneOf 113 | public bool TryPickT0(out T0 value, out OneOf remainder) { ... } 114 | public bool TryPickT1(out T1 value, out OneOf remainder) { ... } 115 | public bool TryPickT2(out T2 value, out OneOf remainder) { ... } 116 | ``` 117 | 118 | The return value indicates if the OneOf contains a T𝑥 or not. If so, then `value` will be set to the inner value from the OneOf. If not, then the remainder will be a OneOf of the remaining generic types. You can use them like this: 119 | 120 | ```csharp 121 | IActionResult Get(string id) 122 | { 123 | OneOf thingOrNotFoundOrError = GetThingFromDb(string id); 124 | 125 | if (thingOrNotFoundOrError.TryPickT1(out NotFound notFound, out var thingOrError)) //thingOrError is a OneOf 126 | return StatusCode(404); 127 | 128 | if (thingOrError.TryPickT1(out var error, out var thing)) //note that thing is a Thing rather than a OneOf 129 | { 130 | _logger.LogError(error.Message); 131 | return StatusCode(500); 132 | } 133 | 134 | return Ok(thing); 135 | } 136 | ``` 137 | 138 | ### Reusable OneOf Types using OneOfBase 139 | 140 | You can declare a OneOf as a type, either for reuse of the type, or to provide additional members, by inheriting from `OneOfBase`. The derived class will inherit the `.Match`, `.Switch`, and `.TryPick𝑥` methods. 141 | 142 | ```csharp 143 | public class StringOrNumber : OneOfBase 144 | { 145 | StringOrNumber(OneOf _) : base(_) { } 146 | 147 | // optionally, define implicit conversions 148 | // you could also make the constructor public 149 | public static implicit operator StringOrNumber(string _) => new StringOrNumber(_); 150 | public static implicit operator StringOrNumber(int _) => new StringOrNumber(_); 151 | 152 | public (bool isNumber, int number) TryGetNumber() => 153 | Match( 154 | s => (int.TryParse(s, out var n), n), 155 | i => (true, i) 156 | ); 157 | } 158 | 159 | StringOrNumber x = 5; 160 | Console.WriteLine(x.TryGetNumber().number); 161 | // prints 5 162 | 163 | x = "5"; 164 | Console.WriteLine(x.TryGetNumber().number); 165 | // prints 5 166 | 167 | x = "abcd"; 168 | Console.WriteLine(x.TryGetNumber().isNumber); 169 | // prints False 170 | ``` 171 | 172 | ### OneOfBase Source Generation 173 | 174 | You can automatically generate `OneOfBase` hierarchies using `GenerateOneOfAttribute` and partial class that extends `OneOfBase` using 175 | a Source Generator (thanks to @romfir for the contribution :D). Install it via 176 | 177 | > Install-Package OneOf.SourceGenerator 178 | 179 | and then define a stub like so: 180 | 181 | ```csharp 182 | [GenerateOneOf] 183 | public partial class StringOrNumber : OneOfBase { } 184 | ``` 185 | 186 | During compilation the source generator will produce a class implementing the OneOfBase boiler plate code for you. e.g. 187 | 188 | ```csharp 189 | public partial class StringOrNumber 190 | { 191 | public StringOrNumber(OneOf.OneOf _) : base(_) { } 192 | 193 | public static implicit operator StringOrNumber(System.String _) => new StringOrNumber(_); 194 | public static explicit operator System.String(StringOrNumber _) => _.AsT0; 195 | 196 | public static implicit operator StringOrNumber(System.Int32 _) => new StringOrNumber(_); 197 | public static explicit operator System.Int32(StringOrNumber _) => _.AsT1; 198 | } 199 | ``` 200 | -------------------------------------------------------------------------------- /licence.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Harry McIntyre 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | --------------------------------------------------------------------------------