Introduction
One of the most anticipated additions to the C# language is Union Types. Developers have relied on inheritance, interfaces, tuples, or custom wrapper classes to represent values that can be one of several different types. C# 14 introduces a cleaner and more expressive approach with Union Types, making code easier to read, maintain, and reason about.
In this article, you'll learn what Union Types are, why they are useful, how they compare with traditional approaches, and how to use them with practical examples.
What Are Union Types?
A Union Type allows a variable, parameter, or return value to hold one of several specified types.
Instead of creating a base class or using the object type, you can explicitly define the allowed types.
Conceptually, a union type looks like this:
string | int
This means the value can be either:
A
stringAn
int
Unlike object, the compiler knows exactly which types are allowed, providing better type safety and IntelliSense support.
Why Developers Needed Union Types
Before Union Types, developers often used less-than-ideal solutions.
Using object
object result = "Success";
Later, the value might become:
result = 200;
The problem is that object can hold anything, making the code harder to understand and requiring runtime type checks.
Using Inheritance
Many projects created base classes solely to represent different outcomes.
abstract class ApiResult { }
class Success : ApiResult { }
class Error : ApiResult { }
While this works, it introduces additional classes and complexity for simple scenarios.
Union Types provide a more direct and readable solution.
Benefits of Union Types
Union Types offer several advantages:
Strong compile-time type safety
Cleaner API design
Reduced boilerplate code
Better IntelliSense support
Easier pattern matching
Improved readability
More expressive return types
These benefits make code easier to maintain, especially in larger applications.
Basic Union Type Example
Imagine a method that returns either an employee ID or an error message.
string | int GetEmployee()
Possible results:
return 101;
or
return "Employee not found";
The method clearly communicates the possible return types without relying on exceptions or wrapper classes.
Working with Pattern Matching
Pattern matching works naturally with Union Types.
Example:
var result = GetEmployee();
switch (result)
{
case int id:
Console.WriteLine($"Employee ID: {id}");
break;
case string message:
Console.WriteLine(message);
break;
}
This makes the code concise and easy to understand.
Practical Example: API Responses
Consider a Web API that retrieves product details.
Instead of returning object, the method can specify exactly what it returns.
Product | NotFound GetProduct(int id)
Usage:
var response = GetProduct(5);
switch (response)
{
case Product product:
Console.WriteLine(product.Name);
break;
case NotFound:
Console.WriteLine("Product not found.");
break;
}
This approach clearly defines all expected outcomes.
Practical Example: Payment Processing
A payment operation can have multiple outcomes.
PaymentSuccess | PaymentFailed ProcessPayment()
Handling the result:
switch (result)
{
case PaymentSuccess success:
Console.WriteLine(success.TransactionId);
break;
case PaymentFailed failed:
Console.WriteLine(failed.Reason);
break;
}
Compared to using exceptions for expected failures, this approach is more explicit and efficient.
Practical Example: File Reading
Suppose you're reading a configuration file.
The operation may return:
File content
File not found
Permission denied
Conceptually:
string | FileNotFound | AccessDenied
Instead of throwing exceptions for common situations, the method can return one of the defined result types, making error handling more predictable.
Union Types vs object
Let's compare the two approaches.

Jasen FiciPosted Jul 29, 2026, 12:24 PM
We shared this in DotNetNews here: https://dotnetnews.co/archive/the-net-news-daily-issue-507/