PolyType 0.16.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package PolyType --version 0.16.1                
NuGet\Install-Package PolyType -Version 0.16.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.16.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add PolyType --version 0.16.1                
#r "nuget: PolyType, 0.16.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.
// Install PolyType as a Cake Addin
#addin nuget:?package=PolyType&version=0.16.1

// Install PolyType as a Cake Tool
#tool nuget:?package=PolyType&version=0.16.1                

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. 
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. With ground-up support for trimming and Native AOT.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.27.1 219 15 days ago
0.26.5 490 24 days ago
0.26.1 287 a month ago
0.25.1 322 a month ago
0.24.1 256 2 months ago
0.23.1 1,247 3 months ago
0.22.1 339 3 months ago
0.21.1 244 3 months ago
0.20.1 205 3 months ago
0.19.3 112 3 months ago
0.19.2 100 3 months ago
0.19.1 101 3 months ago
0.18.2 103 3 months ago
0.18.1 173 3 months ago
0.17.1 162 3 months ago
0.16.10 113 3 months ago
0.16.1 268 4 months ago