0
Modern applications often need to represent values that can have more than one valid form.
For example, an API might return either an integer or a percentage:
42
or:
"25%"
Another API might return one of several possible results, such as a successful response, a validation error, or a not-found result.
Traditionally, C# developers have handled these situations using object, interfaces, inheritance, custom result classes, or other patterns. These approaches are still useful, but they don't always communicate a fixed set of possible values directly through the type system.
C# 15 introduces union types, allowing developers to define a type that can represent one of several specified case types.
Union types are currently part of the .NET 11 and C# 15 development cycle. As of September 2026, .NET 11 is at Release Candidate 1, and union types remain a preview feature, so the final syntax and behavior may change before the stable release.
In this guide, we'll explore what C# 15 union types are, how they work, how they integrate with ASP.NET Core, and where they can be useful in real-world applications.
A union type represents a value that can be one of a predefined set of types.
A simple example is:
public union IntOrString(int, string);
This defines a union named IntOrString that can contain either:
an int
a string
For example:
IntOrString number = 42;
IntOrString text = "25%";
The important part is that the type explicitly defines its possible cases.
This is different from using object:
object value = 42;
An object can represent practically any value type or reference type. The compiler therefore has much less information about what the application actually expects.
With a union:
public union IntOrString(int, string);
the possible alternatives are explicitly defined.
This makes the type itself part of the documentation for the code.
Before C# 15, developers already had several ways to model multiple possible types.
One common approach was object:
public object GetValue()
{
return 42;
}
But the method could potentially return anything.
Another approach was an interface:
public interface INotification
{
}
with several implementations:
public class EmailNotification : INotification
{
}
public class SmsNotification : INotification
{
}
This is a good design when the abstraction should remain extensible.
However, sometimes the application has a deliberately limited set of possible cases.
For example:
EmailNotification
SmsNotification
PushNotification
If those are the only valid cases, a union can express that relationship more directly.
Consider a notification system.
We could define three notification types:
public record EmailNotification(string Address);
public record SmsNotification(string PhoneNumber);
public record PushNotification(string DeviceId);
Then define:
public union Notification(
EmailNotification,
SmsNotification,
PushNotification
);
Now Notification represents one of those three cases.
This is useful because the domain model itself communicates the valid alternatives.
Union types become particularly useful when combined with pattern matching.
For example:
public record Cat(string Name);
public record Dog(string Name);
public record Bird(string Name);
public union Pet(Cat, Dog, Bird);
You can handle the different cases with a switch expression:
Pet pet = new Dog("Rex");
string name = pet switch
{
Dog dog => dog.Name,
Cat cat => cat.Name,
Bird bird => bird.Name
};
Instead of treating the value as an arbitrary object, the code works with the known cases of the union.
This can make domain logic easier to understand, especially when an application has a finite number of possible outcomes.
The most interesting part for web developers is the integration with ASP.NET Core.
ASP.NET Core 11 adds support for C# union types in several JSON-based scenarios.
According to Microsoft's current ASP.NET Core 11 documentation, union types can be used with scenarios including:
Minimal API JSON requests and responses
MVC JSON requests and responses
SignalR JSON hub protocol
Blazor JavaScript interop
Persistent component state
Prerendered component parameters
This makes union types more than just a language feature. They can become part of an application's API and data contracts.
Let's start with a simple example.
Define:
public union IntOrString(int, string);
Then create a Minimal API endpoint:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/value", () =>
{
return new IntOrString(42);
});
app.Run();
The return type tells the application that the value is represented by the IntOrString union.
The same union could represent a string value:
app.MapGet("/percentage", () =>
{
return new IntOrString("25%");
});
This is useful when an API contract genuinely allows multiple representations.
Imagine an API that returns a configuration value called maxUnavailable.
The value might be represented as:
2
or:
25%
We can model it as:
public union IntOrString(int, string);
Then an endpoint can return:
app.MapGet(
"/deployments/{name}/max-unavailable",
IntOrString (string name) =>
{
return Deployments.GetMaxUnavailable(name);
});
The important benefit is that the API contract is more explicit.
Instead of returning object, the endpoint communicates that the result has two possible forms.
Microsoft has used a similar deployment-related example to demonstrate union types in ASP.NET Core.
For ASP.NET Core applications, JSON serialization is an important part of the story.
ASP.NET Core uses System.Text.Json extensively for API request and response processing.
.NET 11 adds support for C# union types in the JSON infrastructure.
For example, consider:
public union IntOrString(int, string);
The active case can be represented as JSON according to the selected value.
An integer case can be represented as:
42
while a string case can be:
"25%"
This allows the API contract to preserve the fact that the value has multiple possible forms.
API documentation is another important consideration.
A good API specification should describe the actual possible responses.
For union types, ASP.NET Core can represent the alternatives through OpenAPI schemas.
Conceptually, an OpenAPI schema may look like:
anyOf:
- type: integer
- type: string
This tells API consumers that the response can be either an integer or a string.
For developers using Swagger or other OpenAPI tooling, this can make API contracts more descriptive.
Instead of documenting an endpoint simply as returning object, the generated API description can communicate the possible alternatives.
Union support isn't limited to Minimal APIs.
ASP.NET Core also supports union types in MVC JSON request and response scenarios.
For example:
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public ProductResult Get(int id)
{
// Return a ProductResult
}
}
A ProductResult could represent different outcomes:
public record Product(int Id, string Name);
public record ProductNotFound(int ProductId);
public union ProductResult(
Product,
ProductNotFound
);
This can make the controller contract easier to understand.
One of the most practical use cases is modeling API results.
Suppose a product lookup can produce three outcomes:
Product found
Product not found
Validation error
We could define:
public record Product(int Id, string Name);
public record ProductNotFound(int ProductId);
public record ProductValidationError(string Message);
public union ProductResult(
Product,
ProductNotFound,
ProductValidationError
);
Now the return type communicates the possible results.
This is often clearer than returning:
object
or creating a generic response wrapper containing many nullable properties.
Union types can also be useful in business software.
Consider an inventory system.
A stock operation might produce one of several results:
Stock updated
Insufficient stock
Invalid product
Warehouse unavailable
We could represent these outcomes as:
public record StockUpdated(int ProductId, decimal Quantity);
public record InsufficientStock(int ProductId);
public record InvalidProduct(int ProductId);
public record WarehouseUnavailable(int WarehouseId);
public union StockResult(
StockUpdated,
InsufficientStock,
InvalidProduct,
WarehouseUnavailable
);
The application service can then return:
public StockResult UpdateStock(int productId, decimal quantity)
{
// Business logic
}
This gives the caller a clear understanding of the possible outcomes.
For large business applications, making these domain outcomes explicit can improve readability and reduce ambiguity.
objectLet's compare the two approaches.
objectpublic object GetValue()
{
return 42;
}
The compiler doesn't know the complete contract.
public union IntOrString(int, string);
Now the contract explicitly defines the allowed alternatives.
This doesn't mean object is bad.
object still has legitimate uses when the data is genuinely dynamic.
The advantage of a union appears when the possible types are known in advance.
Interfaces are still an important part of C# development.
For example:
public interface IPaymentProcessor
{
Task ProcessAsync();
}
An interface is useful when you want an extensible abstraction.
Different implementations can be added later:
public class StripePaymentProcessor : IPaymentProcessor
{
}
public class PayPalPaymentProcessor : IPaymentProcessor
{
}
A union solves a different problem.
If the application intentionally defines a fixed set of alternatives, a union can represent that closed set more directly.
So the decision isn't:
Union types are better than interfaces.
Instead, the question is:
Does this model need to be extensible, or does it represent a known finite set of alternatives?
Traditional inheritance can also model multiple outcomes.
For example:
public abstract class PaymentResult
{
}
public class PaymentSucceeded : PaymentResult
{
}
public class PaymentFailed : PaymentResult
{
}
This is still a perfectly valid design.
A union provides another way to express the same general concept:
public union PaymentResult(
PaymentSucceeded,
PaymentFailed
);
The important distinction is that a union explicitly represents a closed set of cases.
Inheritance can be more appropriate when you need shared behavior, extensibility, or a more traditional object-oriented hierarchy.
SignalR applications can also benefit from union types.
ASP.NET Core 11 supports union types with SignalR's JSON hub protocol.
For example, a real-time application might send different types of events to connected clients.
Instead of using a loosely typed message, the application can define known event cases.
However, developers should pay attention to the specific SignalR protocol being used. The current ASP.NET Core documentation describes union support for the JSON hub protocol, so it shouldn't be assumed that every SignalR serialization protocol behaves identically.
Blazor developers may also benefit from union types.
ASP.NET Core 11 includes union support in several Blazor scenarios involving JSON, including:
JavaScript interop
Persistent component state
Prerendered component parameters
This is particularly interesting for applications that use Blazor and ASP.NET Core together.
A shared union type can potentially provide a more consistent contract between different parts of a full-stack .NET application.
Union support doesn't mean that every ASP.NET Core model-binding scenario automatically supports unions.
The current ASP.NET Core 11 documentation specifically distinguishes JSON-based scenarios from other binding sources.
Developers should not assume that union types automatically work as route, query-string, header, or form-field parameters simply because the union itself is valid C#.
For example:
/products/{id}
and:
/products?value=42
use different ASP.NET Core binding mechanisms from JSON request bodies.
This distinction is important when designing an API.
Union types are most useful when your application has a small, clearly defined set of alternatives.
Good use cases include:
Success
ValidationError
NotFound
Integer
Percentage
Success
InsufficientStock
InvalidProduct
Pending
Approved
Rejected
When an external API explicitly allows several different representations for the same field.
Union types aren't a replacement for every C# abstraction.
You may want to avoid them when:
If new implementations should be added by other parts of the system, an interface may be more appropriate.
If several classes share substantial behavior and state, traditional inheritance may provide a cleaner design.
A union containing a large number of alternatives can become difficult to understand.
Don't introduce a union simply because C# 15 provides the feature.
A new language feature should solve a real design problem.
As of September 2026, C# 15 union types are still a preview feature.
.NET 11 is currently at Release Candidate 1, with general availability expected in November 2026.
This means developers should be careful when adopting union types in production applications before the final release.
Preview features can change before general availability.
For experimentation, learning, and evaluating future architecture, they are worth exploring. For an existing production application, however, it is reasonable to wait for the final .NET 11 release before making major architectural decisions around the feature.
If you want to experiment with C# 15 union types, you'll need a suitable .NET 11 SDK and the appropriate preview language configuration.
For example:
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Because this is still a preview feature, developers should always check the latest Microsoft documentation when experimenting with the current .NET 11 release candidate.
C# 15 union types introduce a new way to model values that can represent one of several known types.
The biggest benefit isn't simply shorter code. It is the ability to make a closed set of possible cases explicit in the type system.
This can be particularly useful for ASP.NET Core applications where APIs need to represent multiple valid outcomes or representations.
With .NET 11, union types are integrated into several areas of the ASP.NET Core ecosystem, including:
Minimal APIs
MVC
JSON serialization
OpenAPI
SignalR
Blazor
However, union types should be viewed as another tool rather than a replacement for object, interfaces, inheritance, or custom result classes.
If your application has a small and well-defined set of possible outcomes, C# 15 union types may provide a cleaner and more explicit way to model them.
Since the feature is still in preview as of September 2026, it is best to experiment with it now while keeping an eye on the final .NET 11 release and Microsoft's updated documentation.
Contact us today to schedule a free, 20-minute call to learn how DotNet Expert Solutions can help you revolutionize the way your company conducts business.
Comments 0