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:
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.
| Feature | Union Types | object |
|---|
| Type Safety | Yes | No |
| IntelliSense | Excellent | Limited |
| Compile-Time Validation | Yes | No |
| Runtime Casting | Minimal | Required |
| Readability | High | Low |
Union Types provide a safer and more maintainable alternative to object.
Union Types vs Inheritance
Inheritance is still valuable for modeling shared behavior, but it isn't always the best choice for representing multiple possible values.
| Scenario | Best Choice |
|---|
| Shared functionality | Inheritance |
| Multiple possible return values | Union Types |
| Domain modeling | Inheritance |
| API response modeling | Union Types |
| Pattern matching | Union Types |
Choosing the right approach depends on the problem you're solving.
Best Practices
When using Union Types, consider these recommendations:
Keep the number of union members small and meaningful.
Use descriptive custom types for complex scenarios.
Prefer pattern matching over manual type checks.
Avoid using Union Types when inheritance better represents shared behavior.
Document the possible outcomes of public APIs.
These practices help keep your codebase clean and understandable.
Common Use Cases
Union Types are particularly useful in:
ASP.NET Core Web APIs
Minimal APIs
Domain-driven design
Microservices
Validation results
Payment processing
File operations
Parser implementations
State management
Result-based programming
As applications grow, these scenarios benefit from explicit and type-safe return values.
Limitations to Consider
Although Union Types simplify many patterns, they are not intended to replace every design technique.
Keep in mind:
Existing projects may require refactoring to adopt them.
Large unions with many possible types can reduce readability.
In some cases, inheritance or interfaces remain the better choice for modeling shared behavior.
Using Union Types where they naturally fit leads to cleaner and more maintainable code.
Conclusion
C# 14 Union Types represent a significant step toward more expressive and type-safe programming. They eliminate many of the workarounds developers have traditionally used, such as object, complex inheritance hierarchies, and custom wrapper classes for simple result handling.
By clearly defining the possible types a variable or method can use, Union Types improve readability, reduce runtime errors, and integrate seamlessly with pattern matching. Whether you're building ASP.NET Core APIs, desktop applications, or cloud-native services, understanding Union Types will help you write cleaner, safer, and more maintainable C# code as the language continues to evolve.