Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
"csharp-12.0/*.md",
"csharp-13.0/*.md",
"csharp-14.0/*.md",
"collection-expression-arguments.md"
"collection-expression-arguments.md",
"unions.md"
],
"src": "_csharplang/proposals",
"dest": "csharp/language-reference/proposals",
Expand Down Expand Up @@ -698,6 +699,7 @@
"_csharplang/proposals/csharp-14.0/extension-operators.md": "Extension operators",
"_csharplang/proposals/csharp-14.0/optional-and-named-parameters-in-expression-trees.md": "Optional and named parameters in expression trees",
"_csharplang/proposals/collection-expression-arguments.md": "Collection expression arguments",
"_csharplang/proposals/unions.md": "Unions",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "C# compiler breaking changes since C# 10",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "C# compiler breaking changes since C# 11",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "C# compiler breaking changes since C# 12",
Expand Down Expand Up @@ -826,6 +828,7 @@
"_csharplang/proposals/csharp-14.0/extension-operators.md": "This proposal extends the proposal for extensions to include *extension operators*, where an operator can be an extension member.",
"_csharplang/proposals/csharp-14.0/optional-and-named-parameters-in-expression-trees.md": "This proposal allows an expression tree to include named and optional parameters. This enables expression trees to be more flexible in how they are constructed.",
"_csharplang/proposals/collection-expression-arguments.md": "This proposal introduces collection expression arguments.",
"_csharplang/proposals/unions.md": "This proposal describes union types and union declarations. Unions allow expressing values from a closed set of types with exhaustive pattern matching.",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "Learn about any breaking changes since the initial release of C# 10 and included in C# 11",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "Learn about any breaking changes since the initial release of C# 11 and included in C# 12",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "Learn about any breaking changes since the initial release of C# 12 and included in C# 13",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// <CaseTypes>
public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);
// </CaseTypes>

// <BasicDeclaration>
public union Pet(Cat, Dog, Bird);
// </BasicDeclaration>

public static class BasicUnionScenario
{
public static void Run()
{
BasicConversion();
PatternMatching();
}

// <BasicConversion>
static void BasicConversion()
{
Pet pet = new Dog("Rex");
Console.WriteLine(pet.Value); // output: Dog { Name = Rex }

Pet pet2 = new Cat("Whiskers");
Console.WriteLine(pet2.Value); // output: Cat { Name = Whiskers }
}
// </BasicConversion>

// <PatternMatching>
static void PatternMatching()
{
Pet pet = new Dog("Rex");

var name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Console.WriteLine(name); // output: Rex
}
// </PatternMatching>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// <BodyMembers>
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
T single => [single],
IEnumerable<T> multiple => multiple,
_ => []
};
}
// </BodyMembers>

public static class BodyMembersScenario
{
public static void Run()
{
BodyMembersExample();
}

// <BodyMembersExample>
static void BodyMembersExample()
{
OneOrMore<string> single = "hello";
OneOrMore<string> multiple = new[] { "a", "b", "c" }.AsEnumerable();

Console.WriteLine(string.Join(", ", single.AsEnumerable())); // output: hello
Console.WriteLine(string.Join(", ", multiple.AsEnumerable())); // output: a, b, c
}
// </BodyMembersExample>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// <ClassUnion>
[System.Runtime.CompilerServices.Union]
public class Result<T> : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;

public Result(T? value) { _value = value; }
public Result(Exception? value) { _value = value; }

public object? Value => _value;
}
// </ClassUnion>

public static class ClassUnionScenario
{
public static void Run()
{
ClassUnionExample();
}

// <ClassUnionExample>
static void ClassUnionExample()
{
Result<string> ok = new Result<string>("success");
Result<string> err = new Result<string>(new InvalidOperationException("failed"));

Console.WriteLine(Describe(ok)); // output: OK: success
Console.WriteLine(Describe(err)); // output: Error: failed

static string Describe(Result<string> result) => result switch
{
string s => $"OK: {s}",
Exception e => $"Error: {e.Message}",
null => "null",
};
}
// </ClassUnionExample>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// <GenericUnion>
public record class None;
public record class Some<T>(T Value);
public union Option<T>(None, Some<T>);
// </GenericUnion>

public static class GenericUnionScenario
{
public static void Run()
{
GenericUnionExample();
}

// <GenericUnionExample>
static void GenericUnionExample()
{
Option<int> some = new Some<int>(42);
Option<int> none = new None();

var result = some switch
{
Some<int> s => $"Has value: {s.Value}",
None => "No value",
};
Console.WriteLine(result); // output: Has value: 42

var result2 = none switch
{
Some<int> s => $"Has value: {s.Value}",
None => "No value",
};
Console.WriteLine(result2); // output: No value
}
// </GenericUnionExample>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// <ManualBasicPattern>
[System.Runtime.CompilerServices.Union]
public struct Shape : System.Runtime.CompilerServices.IUnion
{
private readonly object? _value;

public Shape(Circle value) { _value = value; }
public Shape(Rectangle value) { _value = value; }

public object? Value => _value;
}

public record class Circle(double Radius);
public record class Rectangle(double Width, double Height);
// </ManualBasicPattern>

public static class ManualUnionScenario
{
public static void Run()
{
ManualUnionExample();
}

// <ManualUnionExample>
static void ManualUnionExample()
{
Shape shape = new Shape(new Circle(5.0));

var area = shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
};
Console.WriteLine($"{area:F2}"); // output: 78.54
}
// </ManualUnionExample>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Uncomment when union member providers are available in the compiler:

// <MemberProvider>
// [System.Runtime.CompilerServices.Union]
// public record class Outcome<T> : Outcome<T>.IUnionMembers
// {
// private readonly object? _value;
//
// private Outcome(object? value) => _value = value;
//
// public interface IUnionMembers
// {
// static Outcome<T> Create(T? value) => new(value);
// static Outcome<T> Create(Exception? value) => new(value);
// object? Value { get; }
// }
//
// object? IUnionMembers.Value => _value;
// }
// </MemberProvider>

// <MemberProviderExample>
// public static class MemberProviderScenario
// {
// public static void Run()
// {
// Outcome<string> ok = "success";
// var msg = ok switch
// {
// string s => $"OK: {s}",
// Exception e => $"Error: {e.Message}",
// };
// Console.WriteLine(msg);
// }
// }
// </MemberProviderExample>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// <NonBoxingAccessPattern>
[System.Runtime.CompilerServices.Union]
public struct IntOrBool : System.Runtime.CompilerServices.IUnion
{
private readonly int _intValue;
private readonly bool _boolValue;
private readonly byte _tag; // 0 = none, 1 = int, 2 = bool

public IntOrBool(int? value)
{
if (value.HasValue)
{
_intValue = value.Value;
_tag = 1;
}
}

public IntOrBool(bool? value)
{
if (value.HasValue)
{
_boolValue = value.Value;
_tag = 2;
}
}

public object? Value => _tag switch
{
1 => _intValue,
2 => _boolValue,
_ => null
};

public bool HasValue => _tag != 0;

public bool TryGetValue(out int value)
{
value = _intValue;
return _tag == 1;
}

public bool TryGetValue(out bool value)
{
value = _boolValue;
return _tag == 2;
}
}
// </NonBoxingAccessPattern>

public static class NonBoxingAccessScenario
{
public static void Run()
{
NonBoxingExample();
}

// <NonBoxingExample>
static void NonBoxingExample()
{
IntOrBool val = new IntOrBool((int?)42);

var description = val switch
{
int i => $"int: {i}",
bool b => $"bool: {b}",
};
Console.WriteLine(description); // output: int: 42
}
// </NonBoxingExample>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
public static class NullHandlingScenario
{
public static void Run()
{
NullHandling();
NullableUnionExample();
}

// <NullHandling>
static void NullHandling()
{
Pet pet = default;
Console.WriteLine(pet.Value is null); // output: True

var description = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
Console.WriteLine(description); // output: no pet
}
// </NullHandling>

// <NullableUnionExample>
static void NullableUnionExample()
{
Pet? maybePet = new Dog("Buddy");
Pet? noPet = null;

Console.WriteLine(Describe(maybePet)); // output: Dog: Buddy
Console.WriteLine(Describe(noPet)); // output: no pet

static string Describe(Pet? pet) => pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
null => "no pet",
};
}
// </NullableUnionExample>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
BasicUnionScenario.Run();
GenericUnionScenario.Run();
ValueTypeCasesScenario.Run();
BodyMembersScenario.Run();
NullHandlingScenario.Run();
ManualUnionScenario.Run();
NonBoxingAccessScenario.Run();
ClassUnionScenario.Run();
// Uncomment when union member providers are available in the compiler:
// MemberProviderScenario.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Remove this file when UnionAttribute and IUnion are included in the .NET runtime.
// <RuntimePolyfill>
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;

public interface IUnion
{
object? Value { get; }
}
}
// </RuntimePolyfill>
Loading
Loading