typeshape-csharp
is a practical datatype-generic programming library for .NET types. It is a port of the TypeShape F# library, adapted to patterns and idioms available in C#. See the project website for additional background and API documentation.
You can try the library by installing the typeshape-csharp
NuGet package:
$ dotnet add package typeshape-csharp
which includes the core types and source generator for generating type shapes:
using TypeShape;
[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 TypeShape core abstractions. You can try this out by installing the built-in example libraries:
$ dotnet add package TypeShape.Examples
Here's how the same value can be serialized to three separate formats.
using TypeShape.Examples.JsonSerializer;
using TypeShape.Examples.CborSerializer;
using TypeShape.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 TypeShape providers article for more details on how to use the library with your types.
TypeShape is a meta-library that facilitates rapid development of high performance datatype-generic programs. It exposes a simplified model for .NET types that makes it easy for library authors to publish production-ready components in just a few lines of code. The built-in source generator ensures that any library built on top of the TypeShape abstractions gets Native AOT support for free.
As a library author, TypeShape lets you write high performance, feature complete generic components that target its core abstractions. For example, a parser API using TypeShape might look as follows:
public static class MyFancyParser
{
public static T? Parse<T>(string myFancyFormat) where T : IShapeable<T>;
}
As an end user, TypeShape lets you generate shape models for your own types that can be used with one or more supported libraries:
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:
TypeShape.Examples
project for advanced examples of libraries built on top of TypeShape.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 TypeShape-derived implementation:
Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|---|
Serialize_StjReflection | 254.4 ns | 0.61 ns | 0.51 ns | 1.00 | 0.0048 | 312 B | 1.00 |
Serialize_StjSourceGen | 266.8 ns | 0.25 ns | 0.24 ns | 1.05 | 0.0048 | 312 B | 1.00 |
Serialize_StjSourceGen_FastPath | 129.8 ns | 0.07 ns | 0.06 ns | 0.51 | - | - | 0.00 |
Serialize_TypeShapeReflection | 178.7 ns | 0.08 ns | 0.07 ns | 0.70 | - | - | 0.00 |
Serialize_TypeShapeSourceGen | 179.8 ns | 1.26 ns | 1.11 ns | 0.71 | - | - | 0.00 |
Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|---|
Deserialize_StjReflection | 831.2 ns | 0.83 ns | 0.74 ns | 1.00 | 0.0153 | 992 B | 1.00 |
Deserialize_StjSourceGen | 834.1 ns | 0.62 ns | 0.58 ns | 1.00 | 0.0153 | 968 B | 0.98 |
Deserialize_TypeShapeReflection | 481.5 ns | 1.35 ns | 1.26 ns | 0.58 | 0.0072 | 440 B | 0.44 |
Deserialize_TypeShapeSourceGen | 476.7 ns | 9.47 ns | 8.40 ns | 0.57 | 0.0072 | 440 B | 0.44 |
Even though both serializers target the same underlying reader and writer types, the TypeShape implementation is ~30% faster for serialization and ~40% 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 repo consists of the following projects:
TypeShape
library containing:
TypeShape.SourceGenerator
project contains the built-in source generator implementation.TypeShape.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.TypeShape.Examples
containing library examples:
ConfigurationBinder
like implementation,System.Random
,IEqualityComparer<T>
generator for POCOs and collections,applications
folder contains sample Native AOT console applications.