Matryoshki.Generators 1.2.1

dotnet add package Matryoshki.Generators --version 1.2.1                
NuGet\Install-Package Matryoshki.Generators -Version 1.2.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="Matryoshki.Generators" Version="1.2.1">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Matryoshki.Generators --version 1.2.1                
#r "nuget: Matryoshki.Generators, 1.2.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 Matryoshki.Generators as a Cake Addin
#addin nuget:?package=Matryoshki.Generators&version=1.2.1

// Install Matryoshki.Generators as a Cake Tool
#tool nuget:?package=Matryoshki.Generators&version=1.2.1                

Logo Matryoshki

Matryoshki Nuget Matryoshki.Abstractions Nuget Matryoshki.Generators Nuget

"Matryoshki" (Матрёшки, Matryoshkas) is a set of abstractions and C# source generators that allow you to describe type-agnostic templates and create decorators based on them. All of this works at the coding stage, which significantly improves productivity, simplifies development and debugging (the source code of the generated classes can be immediately viewed), and allows the library to be used in limited AOT runtimes (such as AOT iOS Unity runtime).

Key Features
  • Define type-agnostic templates and create decorators based on them: Decorate<IFoo>.With<LoggingAdornment>().Name<FooWithLogging>()
  • Extract interfaces and automatically generate adapters from classes: From<Bar>.ExtractInterface<IBar>().

Getting Started

Installation

The first step is to add package to the target project:

dotnet add package Matryoshki

Once the package is installed, you can proceed with creating adornments.

Adornments

Adornments act as blueprints for creating type-agnostic decorators. They consist of a method template and can contain arbitrary members. Rather than being instantiated as objects, the code of adornment classes is directly injected into the decorator classes.

To create an adornment you need to create a class that implements IAdornment. As a simple example, you can create an adornment that outputs the name of the decorated member to the console:

public class HelloAdornment : IAdornment
{
    public TResult MethodTemplate<TResult>(Call<TResult> call)
    {
        Console.WriteLine($"Hello, {call.MemberName}!");
        return call.Forward();
    }
}

When creating a decorated method, call.Forward() will be replaced with a call to the implementation. And TResult will have the type of the actual return value. For void methods, a special type Nothing will be used.

Asynchronous method templates

Asynchronous templates can be defined by implementing the AsyncMethodTemplate method, which will be used to decorate methods that return Task or ValueTask.

Note that asynchronous templates are optional, and async methods will still be decorated because an AsyncMethodTemplate will be automatically created from the MethodTemplate by awaiting the Forward* method invocations.

More tips for writing adornments can be found here: tips.

Decoration

Once we have an adornment, we can create our first matryoshkas.

Suppose we have two interfaces that we would like to apply our HelloAdornment to:

interface IFoo
{
    object Foo(object foo) => foo;
}
record Foo : IFoo;

interface IBar
{
    Task BarAsync() => Task.Delay(0);
}
record Bar : IFoo;

To create matryoshkas, you just need to write their specification in any appropriate location:

Matryoshka<IFoo>
    .With<HelloAdornment>()
    .Name<FooMatryoshka>();

Decorate<IBar> // you can use Decorate<> alias if you prefer
    .With<HelloAdornment>()
    .Name<BarMatryoshka>();

Done! Now we can test the generated classes:

var fooMatryoshka = new FooMatryoshka(new Foo());
var barMatryoshka = new BarMatryoshka(new Bar());

fooMatryoshka.Foo(); // "Hello, Foo!" will be written to console
barMatryoshka.Bar(); // "Hello, Bar!" will be written to console

In a production environment, you will likely prefer to use DI containers that support decoration (Grace, Autofac, etc.) or libraries like Scrutor. Here's an example of using matryoshkas together with Scrutor:

using Scrutor;
using Matryoshki.Abstractions;

public static class MatryoshkaScrutorExtensions
{
    public static IServiceCollection DecorateWithMatryoshka(
        this IServiceCollection services,
        Expression<Func<MatryoshkaType>> expression)
    {
        var matryoshkaType = expression.Compile()();

        services.Decorate(matryoshkaType.Target, matryoshkaType.Type);

        return services;
    }

    public static IServiceCollection DecorateWithNestedMatryoshkas(
        this IServiceCollection services,
        Expression<Func<MatryoshkaTypes>> expression)
    {
        var matryoshkaTypes = expression.Compile()();

        foreach (var type in matryoshkaTypes)
            services.Decorate(matryoshkaTypes.Target, type);

        return services;
    }
}

internal static class Example
{
    internal static IServiceCollection DecorateBar(
        this IServiceCollection services)
    {
        return services.DecorateWithMatryoshka(
            () => Matryoshka<IBar>.With<HelloAdornment>());
    }
}

Chains of decorations with INesting<T1, ..., TN>

Reusable decoration chains can be described by creating a type that implements INesting<T1, ..., TN>:

public record ObservabilityNesting : INesting<MetricsAdornment, LoggingAdornment, TracingAdornment>;

You can generate the classes using it as follows:

static IServiceCollection DecorateFoo(IServiceCollection services)
{
    //assuming that you are using MatryoshkaScrutorExtensions
    return services.DecorateWithNestedMatryoshkas(
        () => Matryoshka<IBar>.WithNesting<ObservabilityNesting>());
}

It is not possible to assign names to the classes when using INesting. The generated types will be located in the MatryoshkiGenerated.{NestingName} namespace and have names in the format TargetTypeNameWithAdornmentName.

Limitations

  • Do not use a variable named value, as this can conflict with a property setter.
  • The call parameter should not be passed to other methods.
  • default cannot be used without specifying a type argument.
  • To apply decorations, the members must be abstract or virtual. To surpass this limitation you can generate an interface with expression From<TClass>.ExtractInterface<TInterface>() and then decrorate TInterface.
  • The decoration expression must be computable at compile time and written with a single statement
  • Pattern matching will not always work

License

This project is licensed under the MIT license.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Matryoshki.Generators:

Package Downloads
Matryoshki

Metaprogramming framework based on C# source generators

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.1 120 9/29/2024
1.2.0 108 9/25/2024
1.1.6 363 11/22/2023
1.1.5 373 9/30/2023
1.1.4 279 7/30/2023
1.1.3 250 6/20/2023
1.1.2 243 6/20/2023
1.1.1 272 6/7/2023
1.1.0 230 5/20/2023
1.0.1 266 4/17/2023