Sharper than ever: the Stainless C# SDK generator is now generally available

Stephen Downward
Software Engineer
C# has a unique place in the programming language ecosystem. It powers enterprise software, AAA games, and even front-end web development through Blazor.
Our goal is to generate idiomatic SDKs that work great in all of these environments. Stainless provides high-quality C# code that takes full advantage of the language, and it feels like our SDKs are hand-crafted by an experienced .NET developer. We include things like strong typing, composition with LINQ, and good tooling support. These are things every C# developer expects.
It's a high bar, but it's one we managed to hit with the GA release of our C# SDK generator. Since launching our C# beta last summer, we've added more features while improving stability.
Key highlights
Full .NET Standard 2.0 support
Idiomatic types, conversions, and great editor tooling
Async enumerables for SSE/JSONL and auto-paging
Minimal dependencies
Publishing to NuGet
File uploads and downloads
Sensible equality methods
Built-in generated tests
The ability to make raw requests when needed
Some noteworthy C# generator design decisions
Our goal is to generate idiomatic SDKs that feel at home in each language's ecosystem. We also strive to support advanced features and functionality, even if a particular language doesn't have native support for them. This approach shines through in some of the design decisions we made.
.NET Standard 2.0 support
The .NET ecosystem is splintered. Lots of people use .NET Standard, which has a limited subset of the full .NET functionality common across various .NET implementations, such as on vanilla Windows, UWP, Azure Functions 1.x, Xamarin, and many other runtimes. Others use .NET itself and have access to all of the modern features that make C# great.
The SDKs we generate have wide compatibility and provide the best developer experience possible. On .NET Standard 2.0, our SDKs are still full-featured, and have good DX even though some language features are missing.
On .NET 8 and above, our SDKs are fully idiomatic to the language, and take advantage of the available features.
We accomplished this by considering both stacks carefully as we built our generator. In many cases we were able to accomplish our goals with extensions which allowed us to create record classes that operate as normal classes on .NET Standard. In some cases we rely on preprocessor directives, although this is quite rare, and we ensure we only do this when truly required. As a result, our generated SDKs are still easy to read while supporting both stacks.
Amazing unions
Like many languages, C# does not support unions or sum types natively. There are a lot of tradeoffs when designing unions, and we don't have time to get into them all here. The bottom line is that every language has a unique set of constraints, so careful consideration is needed.
Our solution is for our unions to be backed internally by an object and a JsonElement. The object holds the union variant if one exists, and the JsonElement holds the raw JSON data from the API. This allows us to represent data that doesn’t match the union perfectly, which can be a problem if the OpenAPI schema deviates from the actual API. We have constructors for each possible variant, as well as an unknown variant constructor, which operates as an escape hatch. This can be useful for bypassing the OpenAPI schema when required.
To complement the constructors, we create implicit convertors from the variants. This keeps the DX light, as users don't need to wrap all of their unions. We also generate helper functions for switching and matching, which are inspired by the popular OneOf .NET library.
Basically, the goal was to make unions easy to use without compromising on type-safety or readability. Let’s take a look at an example below, where we have a Dealership SDK, which contains a Vehicle union with Car and Truck variants.
This design delivers strong developer experience without sacrificing type safety or readability, and still allows developers to bypass the OpenAPI schema when needed.
Immutability by default
Many C# SDKs do not use immutable record classes even though mutability leaves room for logic bugs, thread-safety issues, and potentially sub-optimal performance. Part of making a great SDK is preventing these foot-guns wherever possible, so that developers can focus on building software.
Our C# SDKs use immutable record classes which are idiomatic and work around the issues above. Models are record classes, arrays use ImmutableArray, and dictionaries use FrozenDictionary. While looking at existing C# SDKs, we found a lot of prior art of people using mutable classes, like List and Dictionary, inside of supposedly immutable classes. We believe in a higher bar than this and made sure the data structures we generate are truly immutable.
Type-safe models with flexibility
A good SDK should make it difficult, but not impossible, to deviate from the OpenAPI spec. Usually, the spec is correct, and we want it to inform the type system and, by extension, the user. Pragmatically, sometimes the API deviates from its spec. Although there are numerous ways to work around this in the Stainless config, sometimes users still need to be able to use the SDK before this happens. This means our data model needs to support some amount of flexibility.
We work around this by backing our models with an internal read-only dictionary of <string, JsonElement>, which holds the raw data. We immutably expose this to the user so that they can get the raw data if needed. Users can also construct models from their own dictionary, which allows them to bypass the schema when sending requests.
When the user doesn’t need to bypass the schema, they can use the C# properties we generate to access the model’s data. This makes the experience seamless, as if those properties were the actual backing data themselves.
Internally, we cache data so that the user doesn’t have to pay the JSON deserialization cost every time they access the same property. We do this in a thread-safe manner so that the end-user doesn’t have to think about it, and our models still appear entirely immutable.
Here’s what a model might look like:
You would instantiate the object like so:
Or:
Trusted by teams in production
The strongest proof of our SDK quality is real teams shipping production systems that depend on them. Here are a few companies using Stainless-generated C# SDKs today:
Our goal for this release was to make C# a first-class citizen in the Stainless ecosystem, with SDKs that feel at home in real .NET codebases. Our C# generator reflects that philosophy throughout: runtime-aware code generation, idiomatic models, strong typing with pragmatic escape hatches, and a focus on long-term maintainability over short-term convenience.
If you’d like to dig deeper, explore the changelog or start generating SDKs today by signing up for a Stainless account.
We’re excited to see what you build, and we’d love feedback from teams adding C# to their supported languages.
Originally posted
Jan 13, 2026