typeshape-csharp 0.6.1

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

// Install typeshape-csharp as a Cake Tool
#tool nuget:?package=typeshape-csharp&version=0.6.1

typeshape-csharp

Contains a proof-of-concept port of the TypeShape library, adapted to patterns and idioms available in C#. The library provides a .NET datatype model that facilitates developing 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.

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.GetShape<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.GetShape<MyPoco[], Witness>();
ITypeShape<MyPoco[][]> shape = TypeShapeProvider.GetShape<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 TypeShape.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 TypeShape'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
{
    public override object? VisitType<T>(ITypeShape<T> typeShape, object? state)
    {
        // For the sake of simplicity, ignore collection types and just focus on properties/fields.

        // Recursively generate counters for each individual property/field:
        Func<T, int>[] propertyCounters = typeShape.GetProperties(includeFields: true)
            .Where(prop => prop.HasGetter)
            .Select(prop => (Func<T, int>)prop.Accept(this, null)!)
            .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, null)!; // 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 : ITypeShapeProvider<T>
    {
        ITypeShape<T> typeShape = T.GetShape();
        return (Func<T?, int>)typeShape.Accept(s_visitor, null)!;
    }
}

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, TypeShape 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.8.0 52 4/20/2024
0.7.1 44 4/4/2024
0.7.0 31 4/4/2024
0.6.1 80 2/1/2024
0.6.0 89 1/16/2024
0.5.2 111 12/12/2023
0.5.1 102 12/7/2023
0.5.0 105 12/7/2023
0.4.0 118 11/13/2023
0.3.3 108 11/4/2023
0.3.2 120 10/21/2023
0.3.1 116 10/7/2023
0.3.0 89 9/25/2023
0.2.1 109 9/23/2023
0.2.0 111 9/22/2023
0.1.3 97 9/18/2023
0.1.2 104 9/18/2023
0.1.1 100 9/17/2023
0.1.0 87 9/17/2023