DapperLib / DapperLib/DapperAOT

Support non-public constructors

Open
#40 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
472
Forks
43
Avg merge
1d 4h
Merged PRs (30d)
18

Description

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;
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue identifies existing annotated custom-constructor handling and suggests RowFactory as a possible helper location; start there and inspect how constructor accessibility is determined. Verify that private and protected constructors use the new path while accessible constructors remain direct, considering the reflection/Expression and net8+ UnsafeAccessor options shown.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.