Skip to content

Commit 6e5e69c

Browse files
authored
Merge pull request #453 from ignatandrei/450-httpsgithubcomsundewssundewdiscriminatedunions
sundew
2 parents bb12465 + deaf5c3 commit 6e5e69c

28 files changed

Lines changed: 930 additions & 20 deletions

README.md

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# RSCG - 255 Examples of Roslyn Source Code Generators / 16 created by Microsoft /
1+
# RSCG - 256 Examples of Roslyn Source Code Generators / 16 created by Microsoft /
22

3-
The RSCG_Examples repository is a comprehensive documentation system that automatically processes and showcases 255 Roslyn Source Code Generator (RSCG) examples. The system transforms individual RSCG projects into structured documentation with code examples and cross-referenced content with a searchable website and code example exports.
3+
The RSCG_Examples repository is a comprehensive documentation system that automatically processes and showcases 256 Roslyn Source Code Generator (RSCG) examples. The system transforms individual RSCG projects into structured documentation with code examples and cross-referenced content with a searchable website and code example exports.
44

55
This system serves as both a learning resource for .NET developers interested in source generators and an automated pipeline for maintaining up-to-date documentation about the RSCG ecosystem
66

7-
## Latest Update : 2026-02-13 => 13 February 2026
7+
## Latest Update : 2026-02-14 => 14 February 2026
88

99
If you want to see examples with code, please click ***[List V2](https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG)***
1010

@@ -24,8 +24,30 @@ If you want to be notified each time I add a new RSCG example , please click htt
2424

2525
## Content
2626

27-
Those are the 255 Roslyn Source Code Generators that I have tested you can see and download source code example.
27+
Those are the 256 Roslyn Source Code Generators that I have tested you can see and download source code example.
2828
( including 16 from Microsoft )
29+
### 256. [Sundew.DiscriminatedUnions](https://ignatandrei.github.io/RSCG_Examples/v2/docs/Sundew.DiscriminatedUnions) , in the [FunctionalProgramming](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#functionalprogramming) category
30+
31+
Generated on : 2026-02-14 => 14 February 2026
32+
33+
<details>
34+
<summary>Expand</summary>
35+
36+
37+
38+
Author: Kim Hugener Ohlsen
39+
40+
41+
42+
Nuget: [https://www.nuget.org/packages/Sundew.DiscriminatedUnions/](https://www.nuget.org/packages/Sundew.DiscriminatedUnions/)
43+
44+
45+
Link: [https://ignatandrei.github.io/RSCG_Examples/v2/docs/Sundew.DiscriminatedUnions](https://ignatandrei.github.io/RSCG_Examples/v2/docs/Sundew.DiscriminatedUnions)
46+
47+
Source: [https://github.com/sundews/Sundew.DiscriminatedUnions](https://github.com/sundews/Sundew.DiscriminatedUnions)
48+
49+
</details>
50+
2951
### 255. [KnockOff](https://ignatandrei.github.io/RSCG_Examples/v2/docs/KnockOff) , in the [Tests](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#tests) category
3052

3153
Generated on : 2026-02-13 => 13 February 2026

later.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Just later
22

3-
## Latest Update : 2026-02-13 => 13 February 2026
3+
## Latest Update : 2026-02-14 => 14 February 2026
44

55

66

v2/Generator/MultiGeneratorV2.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ public string[] SourceNoRSCG()
227227
text = text.Replace("(skills/", $"({d.Generator!.Source}/skills");
228228

229229
text = text.Replace("Access them as a ReadOnlySpan<byte>", "Access them as a ReadOnlySpan\\<byte\\>");
230+
text = text.Replace("<TUnion>", "\\<TUnion\\>");
230231
text = text.Replace("### ", "##### ");
231232
text = text.Replace("## ", "#### ");
232233
text = text.Replace("# ", "### ");

v2/Generator/all.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,4 @@ Nr,Key,Source,Category
254254
253,FastCloner, https://github.com/lofcz/FastCloner/,Clone
255255
254,ErrorOrX, https://github.com/ANcpLua/ErrorOrX,API
256256
255,KnockOff, https://github.com/NeatooDotNet/KnockOff,Tests
257+
256,Sundew.DiscriminatedUnions, https://github.com/sundews/Sundew.DiscriminatedUnions,FunctionalProgramming

v2/RSCGExamplesData/GeneratorDataRec.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,5 +1546,11 @@
15461546
"Category": 13,
15471547
"dtStart": "2026-02-13T00:00:00",
15481548
"show": true
1549+
},
1550+
{
1551+
"ID":"Sundew.DiscriminatedUnions",
1552+
"Category": 10,
1553+
"dtStart": "2026-02-14T00:00:00",
1554+
"show": true
15491555
}
15501556
]
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
2+
<h1>RSCG nr 256 : Sundew.DiscriminatedUnions</h1>
3+
4+
<h2>Info</h2>
5+
Nuget : <a href="https://www.nuget.org/packages/Sundew.DiscriminatedUnions/" target="_blank">https://www.nuget.org/packages/Sundew.DiscriminatedUnions/</a>
6+
7+
<p>You can find more details at : <a href="https://github.com/sundews/Sundew.DiscriminatedUnions" target="_blank"> https://github.com/sundews/Sundew.DiscriminatedUnions</a></p>
8+
9+
<p>Author :Kim Hugener Ohlsen</p>
10+
11+
<p>Source: <a href="https://github.com/sundews/Sundew.DiscriminatedUnions" target="_blank">https://github.com/sundews/Sundew.DiscriminatedUnions</a> </p>
12+
13+
<h2>About</h2>
14+
15+
Generate tagged union
16+
17+
<h2>
18+
How to use
19+
</h2>
20+
<h3>
21+
Add reference to the <a href="https://www.nuget.org/packages/Sundew.DiscriminatedUnions/" target="_blank">Sundew.DiscriminatedUnions</a> in the csproj
22+
</h3>
23+
<img src="images/Sundew.DiscriminatedUnions/UnionTypesDemo.csproj.png" width="580" height="580" />
24+
25+
<h3>This was for me the <b>starting</b> code</h3>
26+
27+
<br />
28+
I have <b>coded</b> the file Program.cs
29+
<br />
30+
<img src="images/Sundew.DiscriminatedUnions/csFiles/Program.cs.png" width="580" height="580" />
31+
<hr />
32+
33+
<br />
34+
I have <b>coded</b> the file ResultSave.cs
35+
<br />
36+
<img src="images/Sundew.DiscriminatedUnions/csFiles/ResultSave.cs.png" width="580" height="580" />
37+
<hr />
38+
39+
<br />
40+
I have <b>coded</b> the file SaveToDatabase.cs
41+
<br />
42+
<img src="images/Sundew.DiscriminatedUnions/csFiles/SaveToDatabase.cs.png" width="580" height="580" />
43+
<hr />
44+
<h3>And here are the <i>generated</i> files</h3>
45+
46+
<br />
47+
The file <i>generated</i> is UnionTypesDemo.ResultSave.generated.cs
48+
<br />
49+
<img src="images/Sundew.DiscriminatedUnions/generated/UnionTypesDemo.ResultSave.generated.cs.png" width="580" height="580" />
50+
51+
<br />
52+
The file <i>generated</i> is UnionTypesDemo.ResultSave.NotFound.generated.cs
53+
<br />
54+
<img src="images/Sundew.DiscriminatedUnions/generated/UnionTypesDemo.ResultSave.NotFound.generated.cs.png" width="580" height="580" />
55+
56+
<br />
57+
The file <i>generated</i> is UnionTypesDemo.ResultSave.Ok.generated.cs
58+
<br />
59+
<img src="images/Sundew.DiscriminatedUnions/generated/UnionTypesDemo.ResultSave.Ok.generated.cs.png" width="580" height="580" />
60+
61+
<p>
62+
You can download the code and this page as pdf from
63+
<a target="_blank" href='https://ignatandrei.github.io/RSCG_Examples/v2/docs/Sundew.DiscriminatedUnions'>
64+
https://ignatandrei.github.io/RSCG_Examples/v2/docs/Sundew.DiscriminatedUnions
65+
</a>
66+
</p>
67+
68+
69+
<p>
70+
You can see the whole list at
71+
<a target="_blank" href='https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG'>
72+
https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG
73+
</a>
74+
</p>
75+

v2/book/list.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</head>
1818
<body>
1919
<h1>
20-
This is the list of 255 RSCG with examples =>
20+
This is the list of 256 RSCG with examples =>
2121
</h1>
2222

2323
<table >
@@ -1046,6 +1046,10 @@ <h1>
10461046
<td>255</td>
10471047
<td><a href="examples/KnockOff.html">KnockOff</a></td>
10481048
</tr>
1049+
<tr>
1050+
<td>256</td>
1051+
<td><a href="examples/Sundew.DiscriminatedUnions.html">Sundew.DiscriminatedUnions</a></td>
1052+
</tr>
10491053
</table>
10501054

10511055

v2/book/pandocHTML.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ input-files:
269269
- examples/FastCloner.html
270270
- examples/ErrorOrX.html
271271
- examples/KnockOff.html
272+
- examples/Sundew.DiscriminatedUnions.html
272273

273274
# or you may use input-file: with a single value
274275
# defaults:
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"generator":{
3+
"name":"Sundew.DiscriminatedUnions",
4+
"nuget":[
5+
"https://www.nuget.org/packages/Sundew.DiscriminatedUnions/"
6+
],
7+
"link":"https://github.com/sundews/Sundew.DiscriminatedUnions",
8+
"author":"Kim Hugener Ohlsen",
9+
"source":"https://github.com/sundews/Sundew.DiscriminatedUnions"
10+
},
11+
"data":{
12+
"goodFor":["Generate tagged union"],
13+
"csprojDemo":"UnionTypesDemo.csproj",
14+
"csFiles":["Program.cs","ResultSave.cs","SaveToDatabase.cs"],
15+
"excludeDirectoryGenerated":[""],
16+
"includeAdditionalFiles":[""]
17+
},
18+
"links":{
19+
"blog":"",
20+
"video":""
21+
}
22+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Discriminated Unions
2+
3+
Sundew.DiscriminatedUnions implement discriminated unions for C#, until a future version of C# provides it out of the box.
4+
The idea is that this package can be deleted once unions are supported in C#, without requiring changes to switch expressions and statements.
5+
6+
In addition, the project supports dimensional unions through default interface methods (traits).
7+
A dimensional union is a union where cases can be reused in any number of unions, by supporting interface unions through the possibility of implementing multiple interface and default interface members.
8+
9+
## How it works
10+
A Roslyn analyzer asserts and report errors in case switch statements or switch expression do not handle all cases.
11+
C# 8 and 9 already comes with great pattern matching support for evaluation.
12+
13+
In order that the inheritance hierarchy remain closed (All cases in the same assembly), an analyzer ensures that unions are not derived from in referencing assemblies.
14+
Similarly all case classes should be sealed.
15+
16+
Create a union by inheriting from an abstract base (record) class (or interface) marked with the DiscriminatedUnion attribute to build various cases.
17+
Either specify the partial keyword to the union for a source generator to implement factory methods or use the codefix PDU0001 to generate them.
18+
19+
## Sample
20+
### Defining a union
21+
```csharp
22+
[Sundew.DiscriminatedUnions.DiscriminatedUnion]
23+
public abstract partial record Result
24+
{
25+
public sealed partial record Success : Result;
26+
27+
public sealed partial record Warning(string Message) : Result;
28+
29+
public sealed partial record Error(int Code) : Result;
30+
}
31+
```
32+
Alternatively, a union can be defined with unnested case classes and interfaces, allowing the possibility of creating dimensional unions (see below).
33+
34+
### Evaluation
35+
```csharp
36+
var message = result switch
37+
{
38+
Result.Error { Code: > 70 } error => $"High Error code: {error.Code}",
39+
Result.Error error => $"Error code: {error.Code}",
40+
Result.Warning { Message: "Tough warning" } => "Not good",
41+
Result.Warning warning => warning.Message,
42+
Result.Success => "Great",
43+
};
44+
```
45+
46+
### Dimensional unions
47+
To support dimensional unions, unnested cases help because the cases are no longer defined inside a union. However, for this to work the unions are required to declare a factory method named exactly like the case type and that has the CaseType attribute specifying the actual type.
48+
Since version 3, factory methods are generated when the union is declared partial. Alternatively, a code fix (PDU0001) is available to generate the factory methods.
49+
50+
```csharp
51+
[Sundew.DiscriminatedUnions.DiscriminatedUnion]
52+
public partial interface IExpression;
53+
54+
[Sundew.DiscriminatedUnions.DiscriminatedUnion]
55+
public partial interface IArithmeticExpression : IExpression;
56+
57+
[Sundew.DiscriminatedUnions.DiscriminatedUnion]
58+
public partial interface ICommutativeExpression : IArithmeticExpression;
59+
60+
public sealed partial record AdditionExpression(IExpression Lhs, IExpression Rhs) : ICommutativeExpression;
61+
62+
public sealed partial record SubtractionExpression(IExpression Lhs, IExpression Rhs) : IArithmeticExpression;
63+
64+
public sealed partial record MultiplicationExpression(IExpression Lhs, IExpression Rhs) : ICommutativeExpression;
65+
66+
public sealed partial record DivisionExpression(IExpression Lhs, IExpression Rhs) : IArithmeticExpression;
67+
68+
public sealed partial record ValueExpression(int Value) : IExpression;
69+
```
70+
71+
#### Evaluating dimensional unions
72+
With dimensional unions it is possible to handle all cases using a sub union.
73+
As seen in the example below, handling the ArithmeticExpression covers Addition-, Subtraction-, Multiplication- and DivisionExpression.
74+
Typically one would dispatch these to a method handling ArithmeticExpression and where handling all cases would be checked, but it is not required.
75+
This makes it convienient to separate handling logic in smaller chucks of code.
76+
77+
```csharp
78+
public int Evaluate(Expression expression)
79+
{
80+
return expression switch
81+
{
82+
ArithmeticExpression arithmeticExpression => Evaluate(arithmeticExpression),
83+
ValueExpression valueExpression => valueExpression.Value,
84+
};
85+
}
86+
87+
public int Evaluate(ArithmeticExpression arithmeticExpression)
88+
{
89+
return arithmeticExpression switch
90+
{
91+
AdditionExpression additionExpression => Evaluate(additionExpression.Lhs) + Evaluate(additionExpression.Rhs),
92+
SubtractionExpression subtractionExpression => Evaluate(subtractionExpression.Lhs) - Evaluate(subtractionExpression.Rhs),
93+
MultiplicationExpression multiplicationExpression => Evaluate(multiplicationExpression.Lhs) * Evaluate(multiplicationExpression.Rhs),
94+
DivisionExpression divisionExpression => Evaluate(divisionExpression.Lhs) / Evaluate(divisionExpression.Rhs),
95+
};
96+
}
97+
```
98+
99+
#### Enum evaluation
100+
As of version 5.1, regular enums can also use the DiscriminatedUnion attribute causing the analyzer to exhaustively check switch statements and expressions.
101+
102+
## Generator features
103+
As mentioned a source generator is automatically activated for generating factory methods when the partial keyword is specified.
104+
In addition, the DiscriminatedUnion attribute can specify a flags enum (GeneratorFeatures) to control additional code generation.
105+
106+
* Segregate - Generates an extension method for IEnumerable<TUnion> that segregates all items into buckets of the different result.
107+
108+
## Supported diagnostics:
109+
| Diagnostic Id | Description | Code Fix |
110+
| ------------- | ------------------------------------------------------------------------------------------------------------------------- | :------: |
111+
| SDU0001 | Switch does not handled all cases | yes |
112+
| SDU0002 | Switch should not handle default case | yes |
113+
| SDU0003 | Switch has unreachable null case | yes |
114+
| SDU0004 | Class unions must be abstract | yes |
115+
| SDU0005 | Only unions can extended other unions | no |
116+
| SDU0006 | Unions cannot be extended outside their assembly | no |
117+
| SDU0007 | Cases must be declared in the same assembly as their unions | no |
118+
| SDU0008 | Cases should be sealed | yes |
119+
| SDU0009 | Unnested cases should have factory method | PDU0001 |
120+
| SDU0010 | Factory method should have correct CaseTypeAttribute | yes |
121+
| SDU0011 | Reported when a case is implemented by throwing NotImplementedException, because CodeCleanup may siliently 'fix' SDU0001. | yes |
122+
| SDU0012 | Reported when a case contains type parameters that are not in the union type parameter list. | yes |
123+
| PDU0001 | Make union/case partial for code generator | yes |
124+
| PDU0002 | Populate union factory methods | yes |
125+
| SDU9999 | Switch should throw in default case | no |
126+
| GDU0001 | Discriminated union declaration could not be found | no |
127+
128+
## Issues/Todos
129+
* Switch appears with red squiggly lines in VS: https://github.com/dotnet/roslyn/issues/57041
130+
* Nullability is falsely evaluated when the switch hints null is possible: https://github.com/dotnet/roslyn/issues/57042

0 commit comments

Comments
 (0)