PolyType is a practical generic programming library for .NET. It facilitates the rapid development of feature-complete, high-performance libraries that interact with user-defined types. This includes serializers, RPC frameworks, structured loggers, mappers, validators, parsers, random generators, and equality comparers. Its built-in source generator ensures that any library built on top of PolyType gets Native AOT support for free.
The project is a port of the TypeShape library for F#, adapted to patterns and idioms available in C#. The name PolyType is a reference to polytypic programming, another term for generic programming.
See the project website for additional background and API documentation.
You can try the library by installing the PolyType
NuGet package:
$ dotnet add package PolyType
which includes the core types and source generator for generating type shapes:
using PolyType;
[GenerateShape]
public partial record Person(string name, int age);
Doing this will augment Person
with an implementation of the IShapeable<Person>
interface. This suffices to make Person
usable with any library that targets the PolyType core abstractions. You can try this out by installing the built-in example libraries:
$ dotnet add package PolyType.Examples
Here's how the same value can be serialized to three separate formats.
using PolyType.Examples.JsonSerializer;
using PolyType.Examples.CborSerializer;
using PolyType.Examples.XmlSerializer;
Person person = new("Pete", 70);
JsonSerializerTS.Serialize(person); // {"Name":"Pete","Age":70}
XmlSerializer.Serialize(person); // <value><Name>Pete</Name><Age>70</Age></value>
CborSerializer.EncodeToHex(person); // A2644E616D656450657465634167651846
Since the application uses a source generator to produce the shape for Person
, it is fully compatible with Native AOT. See the shape providers article for more details on how to use the library with your types.
As a library author, PolyType makes it easy to write high-performance, feature-complete components by targeting its core abstractions. For example, a parser API using PolyType might look as follows:
public static class MyFancyParser
{
public static T? Parse<T>(string myFancyFormat) where T : IShapeable<T>;
}
The IShapeable<T>
constraint indicates that the parser only works with types augmented with PolyType metadata. This metadata can be provided using the PolyType source generator:
Person? person = MyFancyParser.Parse<Person>(format); // Compiles
[GenerateShape] // Generate an IShapeable<TPerson> implementation
partial record Person(string name, int age, List<Person> children);
For more information see:
- The core abstractions document for an overview of the core programming model.
- The shape providers document for an overview of the built-in shape providers and their APIs.
- The generated API documentation for the project.
- The
PolyType.Examples
project for advanced examples of libraries built on top of PolyType.
The repo includes a JSON serializer built on top of the Utf8JsonWriter
/Utf8JsonReader
primitives provided by System.Text.Json. At the time of writing, the full implementation is just under 1200 lines of code but exceeds STJ's built-in JsonSerializer
both in terms of supported types and performance.
Here's a benchmark comparing System.Text.Json
with the included PolyType implementation:
Method | Mean | Ratio | Allocated | Alloc Ratio |
---|---|---|---|---|
Serialize_StjReflection | 150.43 ns | 1.00 | 312 B | 1.00 |
Serialize_StjSourceGen | 151.31 ns | 1.01 | 312 B | 1.00 |
Serialize_StjSourceGen_FastPath | 96.79 ns | 0.64 | - | 0.00 |
Serialize_PolyTypeReflection | 113.19 ns | 0.75 | - | 0.00 |
Serialize_PolyTypeSourceGen | 112.92 ns | 0.75 | - | 0.00 |
Method | Mean | Ratio | Allocated | Alloc Ratio |
---|---|---|---|---|
Deserialize_StjReflection | 534.0 ns | 1.00 | 1016 B | 1.00 |
Deserialize_StjSourceGen | 534.6 ns | 1.00 | 992 B | 0.98 |
Deserialize_PolyTypeReflection | 273.1 ns | 0.51 | 440 B | 0.43 |
Deserialize_PolyTypeSourceGen | 266.3 ns | 0.50 | 440 B | 0.43 |
Even though both serializers target the same underlying reader and writer types, the PolyType implementation is ~75% faster for serialization and ~100% faster for deserialization, when compared with System.Text.Json's metadata serializer. As expected, fast-path serialization is still fastest since its implementation is fully inlined.
The following projects have been based on PolyType:
- Nerdbank.MessagePack - a MessagePack library with performance to rival MessagePack-CSharp, and greater simplicity and additional features.
- “At Alvys, we built an EDI mapping engine to automate and accelerate EDI integrations. The engine had to be fast, configurable and portable across EDI versions. A crucial step is mapping the EDI segments model to an object model. For this, we used PolyType, which allowed us to easily build both the encoder and the decoder. We appreciated the emphasis on fundamentals and performance, as well as the plethora of examples. The result: a solution with only a fraction of the code and an order-of-magnitude increase in micro-benchmark performance compared to the previous solution that used a commercial EDI library.” — Leo Gorodinski
The repo consists of the following projects:
- The core
PolyType
library containing:- The core abstractions defining the type model.
- The reflection provider implementation.
- The model classes used by the source generator.
- The
PolyType.SourceGenerator
project contains the built-in source generator implementation. - The
PolyType.Roslyn
library exposes a set of components for extracting data models from Roslyn type symbols. Used as the foundation for the built-in source generator. PolyType.Examples
containing library examples:- A serializer built on top of System.Text.Json,
- A serializer built on top of System.Xml,
- A serializer built on top of System.Formats.Cbor,
- A
ConfigurationBinder
like implementation, - A dependency injection implementation,
- A simple pretty-printer for .NET values,
- A generic random value generator based on
System.Random
, - A JSON schema generator for .NET types,
- An object cloning function,
- A structural
IEqualityComparer<T>
generator for POCOs and collections, - An object validator in the style of System.ComponentModel.DataAnnotations.
- A simple .NET object mapper.
- The
applications
folder contains sample Native AOT console applications.
CI builds of NuGet packages are available on feedz.io. To use the feed, add the following package source to your NuGet.config
:
<configuration>
<packageSources>
<add key="feedz.io" value="https://f.feedz.io/eiriktsarpalis/PolyType/nuget/index.json" />
</packageSources>