PolyType 0.18.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package PolyType --version 0.18.1
                    
NuGet\Install-Package PolyType -Version 0.18.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PolyType" Version="0.18.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PolyType" Version="0.18.1" />
                    
Directory.Packages.props
<PackageReference Include="PolyType" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PolyType --version 0.18.1
                    
#r "nuget: PolyType, 0.18.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=PolyType&version=0.18.1
                    
Install as a Cake Addin
#tool nuget:?package=PolyType&version=0.18.1
                    
Install as a Cake Tool

PolyType

Contains a port of the TypeShape F# library, adapted to patterns and idioms available in C#. It provides a type model that facilitates development of high-performance datatype-generic components such as serializers, loggers, transformers and validators. At its core, the programming model employs a variation on the visitor pattern that enables strongly-typed traversal of arbitrary type graphs: it can be used to generate object traversal algorithms that incur zero allocation cost. See the project website for additional guides and API documentation.

The project includes two shape model providers: one reflection derived and one source generated. It follows that any datatype-generic application built on top of the shape model gets trim safety/NativeAOT support for free once it targets source generated models.

Using the library

Users can extract the shape model for a given type either using the built-in source generator:

ITypeShape<MyPoco> shape = TypeShapeProvider.Resolve<MyPoco>();

[GenerateShape] // Auto-generates a static abstract factory for ITypeShape<MyPoco>
public partial record MyPoco(string x, string y);

For types not accessible in the current compilation, the implementation can be generated using a separate witness type:

ITypeShape<MyPoco[]> shape = TypeShapeProvider.Resolve<MyPoco[], Witness>();
ITypeShape<MyPoco[][]> shape = TypeShapeProvider.Resolve<MyPoco[][], Witness>();

// Generates factories for both ITypeShape<MyPoco[]> and ITypeShape<MyPoco[][]>
[GenerateShape<MyPoco[]>]
[GenerateShape<MyPoco[][]>]
public partial class Witness;

The library also provides a reflection-based provider:

using PolyType.ReflectionProvider;

ITypeShape<MyPoco> shape = ReflectionTypeShapeProvider.Default.GetShape<MyPoco>();
public record MyPoco(string x, string y);

In both cases the providers will generate a strongly typed datatype model for MyPoco. Models for types can be fed into datatype-generic consumers that can be declared using PolyType's visitor pattern.

Example: Writing a datatype-generic counter

The simplest possible example of a datatype-generic programming is counting the number of nodes that exist in a given object graph. This can be implemented by extending the TypeShapeVisitor class:

public sealed partial class CounterVisitor : TypeShapeVisitor
{
    // For the sake of simplicity, ignore collection types and just focus on properties/fields.
    public override object? VisitObject<T>(IObjectTypeShape<T> objectShape, object? state)
    {
        // Recursively generate counters for each individual property/field:
        Func<T, int>[] propertyCounters = objectShape.GetProperties()
            .Where(prop => prop.HasGetter)
            .Select(prop => (Func<T, int>)prop.Accept(this)!)
            .ToArray();

        // Compose into a counter for the current type.
        return new Func<T?, int>(value =>
        {
            if (value is null)
                return 0;

            int count = 1; // the current node itself
            foreach (Func<T, int> propertyCounter in propertyCounters)
                count += propertyCounter(value);

            return count;
        });
    }

    public override object? VisitProperty<TDeclaringType, TPropertyType>(IPropertyShape<TDeclaringType, TPropertyType> propertyShape, object? state)
    {
        Getter<TDeclaringType, TPropertyType> getter = propertyShape.GetGetter(); // extract the getter delegate
        var propertyTypeCounter = (Func<TPropertyType, int>)propertyShape.PropertyType.Accept(this)!; // extract the counter for the property type
        return new Func<TDeclaringType, int>(obj => propertyTypeCounter(getter(ref obj))); // compose to a property-specific counter
    }
}

We can now define a counter factory using the visitor:

public static class Counter
{
    private readonly static CounterVisitor s_visitor = new();

    public static Func<T?, int> CreateCounter<T>() where T : IShapeable<T>
    {
        ITypeShape<T> typeShape = T.GetShape();
        return (Func<T?, int>)typeShape.Accept(s_visitor)!;
    }
}

That we can then apply to the shape of our POCO like so:

Func<MyPoco?, int> pocoCounter = Counter.CreateCounter<MyPoco>();

[GenerateShape]
public partial record MyPoco(string? x, string? y);

In essence, PolyType uses the visitor to fold a strongly typed Func<MyPoco?, int> counter delegate, but the delegate itself doesn't depend on the visitor once invoked: it only defines a chain of strongly typed delegate invocations that are cheap to invoke once constructed:

pocoCounter(new MyPoco("x", "y")); // 3
pocoCounter(new MyPoco("x", null)); // 2
pocoCounter(new MyPoco(null, null)); // 1
pocoCounter(null); // 0

For more details, please consult the README file at the project page.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on PolyType:

Package Downloads
PolyType.TestCases

Practical generic programming for C#

PolyType.Examples

Practical generic programming for C#

Nerdbank.MessagePack

A fast and more user-friendly MessagePack serialization library for .NET and .NET Framework. This package is brought to you by one of the two major contributors to MessagePack-CSharp. As its natural successor, this library comes packed with features that its predecessor lacks, and has ongoing support. Premium support for trimming and Native AOT, secure deserialization of untrusted data, async serialization, streaming deserialization, skip serializing of default values, reference preservation, and support for reference cycles. Also features an automatic structural equality API.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.45.1 195 6/27/2025
0.44.1 176 6/26/2025
0.43.1 174 6/26/2025
0.42.1 2,390 6/10/2025
0.41.1 405 6/3/2025
0.40.1 354 5/23/2025
0.39.1 190 5/22/2025
0.38.1 196 5/20/2025
0.37.1 595 4/30/2025
0.36.1 326 4/21/2025
0.35.1 228 4/20/2025
0.34.1 309 3/31/2025
0.33.1 292 3/28/2025
0.32.1 537 3/25/2025
0.31.1 318 3/18/2025
0.30.1 177 3/14/2025
0.29.3 385 3/5/2025
0.29.1 372 3/4/2025
0.28.5 159 3/1/2025
0.28.1 202 3/1/2025
0.27.1 343 2/13/2025
0.26.5 802 2/3/2025
0.26.1 363 1/23/2025
0.25.1 10,638 1/19/2025
0.24.1 501 12/27/2024
0.23.1 1,355 12/12/2024
0.22.1 354 12/5/2024
0.21.1 257 11/26/2024
0.20.1 223 11/23/2024
0.19.3 127 11/19/2024
0.19.2 119 11/19/2024
0.19.1 117 11/19/2024
0.18.2 117 11/18/2024
0.18.1 187 11/17/2024
0.17.1 179 11/16/2024
0.16.10 129 11/15/2024
0.16.1 305 11/8/2024