C#  

C# 14 Union Types with Practical Examples

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 string

  • An 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.

FeatureUnion Typesobject
Type SafetyYesNo
IntelliSenseExcellentLimited
Compile-Time ValidationYesNo
Runtime CastingMinimalRequired
ReadabilityHighLow

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.

ScenarioBest Choice
Shared functionalityInheritance
Multiple possible return valuesUnion Types
Domain modelingInheritance
API response modelingUnion Types
Pattern matchingUnion 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.