DapperLib / DapperAOT

Build time tools in the flavor of Dapper
Other
357 stars 19 forks source link

Support non-public constructors #40

Open mgravell opened 12 months ago

mgravell commented 12 months ago

We already support annotated custom constructors when they're accessible to the generator. We can extend that.

Note: only impacts private and protected constructor usage; all others should continue using direct

This can be implemented acceptably using reflection (ideally optimized via Expression) or [UnsafeAccessor] (net8+ only); example

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;

static class P {
    static void Main()
    {
        var obj = CreateBar(42);
        Console.WriteLine(obj.A);

        obj = CreateBar(96);
        Console.WriteLine(obj.A);
    }
#if NET8_0_OR_GREATER

    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
    static extern Bar CreateBar(int a);
#else
    private static Func<int, Bar>? s_CreateBar;
    static Bar CreateBar(int a) => SomeUtilityHelper.GetConstructor(ref s_CreateBar)(a);
#endif
}

static class SomeUtilityHelper // in DapperAOT - maybe in RowFactory?
{
    public static TDelegate GetConstructor<TDelegate>(ref TDelegate? field) where TDelegate : Delegate
    {
        return field ?? SlowCreate(ref field);

        static TDelegate SlowCreate(ref TDelegate? field)
        {
            var signature = typeof(TDelegate).GetMethod(nameof(Action.Invoke));
            if (signature?.ReturnType is null || signature.ReturnType == typeof(void))
            {
                throw new InvalidOperationException("No target-type found");
            }
            var methodArgs = signature.GetParameters();
            var argTypes = Array.ConvertAll(methodArgs, p => p.ParameterType);
            var ctor = signature.ReturnType.GetConstructor(
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, argTypes);
            if (ctor is null)
            {
                throw new InvalidOperationException("No suitable constructor found matching "
                    + string.Join<Type>(", ", argTypes));
            }
            var args = Array.ConvertAll(methodArgs, p => Expression.Parameter(p.ParameterType, p.Name));
            field = Expression.Lambda<TDelegate>(Expression.New(ctor, args), args).Compile();
            return field;
        }
    }
}
class Bar
{
    private readonly int x;
    public int A => x;
    private Bar(int a) => x = a;
}