Получите Func <> из MethodInfo с частными (недоступными) типами

Рассмотрим следующий код:

private class ThirdPartyClass {
    private class InternalPrivateClass { }
    private static InternalPrivateClass Init() { 
        return new InternalPrivateClass(); 
    }
    private static int DoSomething(InternalPrivateClass t1) { 
        return 0; 
    }
}

Предположим, у меня нет контроля над ThirdPartyClass, и его обратное проектирование обходится слишком дорого. Я хочу иметь возможность быстро вызывать DoSomething без накладных расходов на производительность, связанных с отражением. Итак, что у меня есть на данный момент:

Type t = typeof(ThirdPartyClass);
object context = t.GetMethod("Init", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null);
MethodInfo mi = t.GetMethod("DoSomething", BindingFlags.NonPublic | BindingFlags.Static);
// ...now what?
  • Вызов mi.Invoke(null, new object[]{context}), конечно, происходит медленно, потому что он использует отражение.
  • Delegate.CreateDelegate(typeof(Func<object, int>), mi); не работает, потому что подпись Func должна точно соответствовать, а (object, int) не соответствует подписи MethodInfo (ThirdPartyClass.InternalPrivateClass, int)
  • Создание правильно типизированного делегата посредством отражения (см. https://stackoverflow.com/a/40579063/2692950) позволяет мне вызывать только .DynamicInvoke(context), который все еще работает медленно. Я не могу передать этот делегат в Func, чтобы иметь возможность вызывать его напрямую, потому что, опять же, подписи не совпадают.
  • Я не могу писать Func<ThirdPartyClass.InternalPrivateClass, int> - он не компилируется, так как InternalPrivateClass частный.

Решено! (https://stackoverflow.com/a/52652398/2692950)

Пример, зачем мне это нужно:

Взгляните на эту реализацию хэша MD4: https://stackoverflow.com/a/46821287/2692950 (Укороченная версия: https://stackoverflow.com/a/52640221/2692950)

Это работает очень хорошо, за исключением того, что каждая операция хеширования вызывает метод через отражение!

В этом примере мы вызываем через отражение недоступную частную функцию System.Security.Cryptography.Utils.HashEnd(SafeProvHandle h), передавая SafeHandle в качестве параметра. Это работает, потому что SafeProvHandle наследуется от SafeHandle. На SafeProvHandle нельзя ссылаться напрямую, потому что он частный, поэтому, похоже, нет никакого способа вызвать эту функцию напрямую.

(Меня больше всего интересует, существует ли решение для общего случая в верхней части вопроса, но если кто-нибудь знает лучший способ реализовать получение поставщика криптографических услуг непосредственно с помощью ALG_ID, я все слышу :)

Косвенное решение: сторонняя библиотека кажется .NET. К счастью, вы можете скопировать исходный код из linksource.microsoft.com/#mscorlib/system/security/…, сделать его скомпилированным (что не обязательно легко), а затем изменить его по мере необходимости.

MineR 04.10.2018 05:16

Это в значительной степени потребовало бы повторной реализации хорошей части .NET. Есть гораздо более простые способы разобраться с моим конкретным примером. Меня больше интересует общий случай. Я понимаю, что, скорее всего, прошу чего-то, чего не существует, но попробовать не помешает :)

Duke Nukem 04.10.2018 07:46

Я не думаю, что есть способ. Вы уже (я думаю) осознали, что объявить переменную не может удерживать то, что вы пытаетесь создать. Следующим шагом будет создание некой функции-оболочки, которая может вызывать желаемую функцию, но сама по себе не должна передавать InternalPrivateClass. Что вы можете хранить какой-то Func, готовый его вызвать. Но выполнение этой функции сталкивается со всеми теми же препятствиями.

Damien_The_Unbeliever 04.10.2018 08:15

Я добавил параграф, показывающий, как частный метод, который ожидает частный тип в качестве параметра, вызывается без прямой ссылки на частный тип. Похоже, что решение этой проблемы, если оно существует, действительно включало бы испускание IL. К сожалению, я недостаточно знаю об этой области.

Duke Nukem 04.10.2018 08:38

Является ли частный класс, который вам нужно создать, таким же простым, как в приведенном вами примере? т.е. общедоступный конструктор без параметров? Это реальный код, который вы хотите скомпилировать (если мы проигнорируем частные модификаторы): var x = thirdPartyInstance.SomeFunc(new ThirdPartyClass.InternalPrivateClass());? Я спрашиваю, поскольку у меня есть некоторый опыт работы с излучением IL, я мог бы попытаться вместе сделать небольшую попытку сделать именно это.

Lasse V. Karlsen 04.10.2018 08:41

На самом деле я никогда не создаю экземпляр InternalPrivateClass. Скорее, я сохраняю ссылку на него, используя его базовый класс. Вот более простая версия примера кода: stackoverflow.com/a/52640221/2692950. Там я в значительной степени ищу способ .Invoke делегата напрямую вместо .DynamicInvoke

Duke Nukem 04.10.2018 08:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
172
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это немного сложно сделать, но это можно сделать с помощью DynamicMethod в пространстве имен System.Reflection.Emit. Это позволяет нам испускать IL во время выполнения, которое вызывает эти методы, без необходимости ссылаться на действительные, видимые идентификаторы в нашем коде. Один из приемов, который может использовать этот класс, - пропускать различные проверки безопасности и видимости, которые мы устанавливаем с помощью параметров в конструкторе. Из примера нам нужно заменить класс Utils. Вот его переписывание с использованием DynamicMethod для создания делегатов:

internal static class DelegateUtils
{
    private static readonly Type UtilsType = Type.GetType("System.Security.Cryptography.Utils");
    private static readonly Func<int, SafeHandle> CreateHashDel;
    private static readonly Action<SafeHandle, byte[], int, int> HashDataDel;
    private static readonly Func<SafeHandle, byte[]> EndHashDel;

    static DelegateUtils()
    {
        CreateHashDel = CreateCreateHashDelegate();
        HashDataDel = CreateHashDataDelegate();
        EndHashDel = CreateEndHashDelegate();
    }

    internal static SafeHandle CreateHash(int algid)
    {
        return CreateHashDel(algid);
    }

    internal static void HashData(SafeHandle h, byte[] data, int ibStart, int cbSize)
    {
        HashDataDel(h, data, ibStart, cbSize);
    }

    internal static byte[] EndHash(SafeHandle h)
    {
        return EndHashDel(h);
    }

    private static Func<int, SafeHandle> CreateCreateHashDelegate()
    {
        var prop = UtilsType.GetProperty("StaticProvHandle", BindingFlags.NonPublic | BindingFlags.Static);

        var createHashMethod = UtilsType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
            .FirstOrDefault(mi => mi.Name == "CreateHash" && mi.GetParameters().Length == 2);

        var createHashDyn = new DynamicMethod("CreateHashDyn", typeof(SafeHandle), new[] { typeof(int) }, typeof(object), true);
        var ilGen = createHashDyn.GetILGenerator();
        ilGen.Emit(OpCodes.Call, prop.GetGetMethod(true));
        ilGen.Emit(OpCodes.Ldarg_0);
        ilGen.Emit(OpCodes.Call, createHashMethod);
        ilGen.Emit(OpCodes.Ret);

        var del = (Func<int, SafeHandle>)createHashDyn.CreateDelegate(typeof(Func<int, SafeHandle>));
        return del;
    }

    private static Action<SafeHandle, byte[], int, int> CreateHashDataDelegate()
    {
        var hashDataMethod = UtilsType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
            .FirstOrDefault(mi => mi.Name == "HashData" && mi.GetParameters().Length == 4);
        var hashDataDyn = new DynamicMethod("HashDataDyn", typeof(void), new[] { typeof(SafeHandle), typeof(byte[]), typeof(int), typeof(int) }, typeof(object), true);
        var ilGen = hashDataDyn.GetILGenerator();
        ilGen.Emit(OpCodes.Ldarg_0);
        ilGen.Emit(OpCodes.Ldarg_1);
        ilGen.Emit(OpCodes.Ldarg_2);
        ilGen.Emit(OpCodes.Ldarg_3);
        ilGen.Emit(OpCodes.Call, hashDataMethod);
        ilGen.Emit(OpCodes.Ret);

        var del = (Action<SafeHandle, byte[], int, int>)hashDataDyn.CreateDelegate(typeof(Action<SafeHandle, byte[], int, int>));
        return del;
    }

    private static Func<SafeHandle, byte[]> CreateEndHashDelegate()
    {
        var endHashMethod = UtilsType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
            .FirstOrDefault(mi => mi.Name == "EndHash" && mi.GetParameters().Length == 1);
        var endHashDyn = new DynamicMethod("EndHashDyn", typeof(byte[]), new[] { typeof(SafeHandle) }, typeof(object), true);
        var ilGen = endHashDyn.GetILGenerator();
        ilGen.Emit(OpCodes.Ldarg_0);
        ilGen.Emit(OpCodes.Call, endHashMethod);
        ilGen.Emit(OpCodes.Ret);

        var del = (Func<SafeHandle, byte[]>)endHashDyn.CreateDelegate(typeof(Func<SafeHandle, byte[]>));
        return del;
    }
}

Теперь вопрос в том, насколько это дает преимущество в скорости. Это дает вам что-то вроде увеличения в 2-4 раза в зависимости от размера данных, которые вы хешируете. Чем меньше размер, тем выше скорость прироста, вероятно, потому, что мы потратили меньше времени на вычисления и больше времени между вызовами методов. Вот результаты быстрого теста:

BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.286 (1803/April2018Update/Redstone4)
Intel Core i5-4200U CPU 1.60GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
Frequency=2240904 Hz, Resolution=446.2485 ns, Timer=TSC
[Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3163.0
DefaultJob : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.3163.0

Method | N | Mean | Error | StdDev |
----------- |------ |----------:|----------:|----------:|
Reflection | 1000 | 16.239 us | 0.1252 us | 0.1046 us |
Delegate | 1000 | 4.329 us | 0.0245 us | 0.0230 us |
Reflection | 10000 | 31.832 us | 0.1599 us | 0.1335 us |
Delegate | 10000 | 19.703 us | 0.1005 us | 0.0940 us |

Обратите внимание, что N - это количество хешируемых байтов. Это использует весь код, предоставленный в ссылках OP, для создания реализации MD4, а затем вызывает ComputeHash для этого.

Код теста:

public class MD4DelegateVsReflection
{
    private MD4 md4 = MD4.Create();
    private byte[] data;

    [Params(1000, 10000)]
    public int N;

    public void SetupData()
    {
        data = new byte[N];
        new Random(42).NextBytes(data);
    }

    [GlobalSetup(Target = nameof(Reflection))]
    public void ReflectionSetup()
    {
        MD4.SetReflectionUtils();
        SetupData();
    }

    [GlobalSetup(Target = nameof(Delegate))]
    public void DelegateSetup()
    {
        MD4.SetDelegateUtils();
        SetupData();
    }

    [Benchmark]
    public byte[] Reflection() => md4.ComputeHash(data);

    [Benchmark]
    public byte[] Delegate() => md4.ComputeHash(data);
}

Большое спасибо, это было именно то, что мне было нужно! Используя ваш пример, я изменил свой помощник CreateDelegate, чтобы иметь возможность обрабатывать общий случай в моем OP. Я даю вам общепринятый ответ за то, что вы показали мне, как это было возможно. Если вы считаете, что мое решение общего случая, приведенное ниже, должно быть помечено как принятое, пожалуйста, дайте мне знать.

Duke Nukem 04.10.2018 19:20

Вот общее решение для создания Func <> или Action <> из MethodInfo с недоступными типами:

public static Delegate CreateDelegate(this MethodInfo methodInfo, object target, params Type[] custTypes) {
    Func<Type[], Type> getType;
    bool isAction = methodInfo.ReturnType.Equals((typeof(void))), cust = custTypes.Length > 0;
    Type[] types = cust ? custTypes : methodInfo.GetParameters().Select(p => p.ParameterType).ToArray();
    if (isAction) getType = Expression.GetActionType;
    else {
        getType = Expression.GetFuncType;
        if (!cust) types = types.Concat(new[] { methodInfo.ReturnType }).ToArray();
    }
    if (cust) {
        int i, nargs = types.Length - (isAction ? 0 : 1);
        var dm = new DynamicMethod(methodInfo.Name, isAction ? typeof(void) : types.Last(), types.Take(nargs).ToArray(), typeof(object), true);
        var il = dm.GetILGenerator();
        for (i = 0; i < nargs; i++)
            il.Emit(OpCodes.Ldarg_S, i);
        il.Emit(OpCodes.Call, methodInfo);
        il.Emit(OpCodes.Ret);
        if (methodInfo.IsStatic) return dm.CreateDelegate(getType(types));
        return dm.CreateDelegate(getType(types), target);
    }
    if (methodInfo.IsStatic) return Delegate.CreateDelegate(getType(types), methodInfo);
    return Delegate.CreateDelegate(getType(types), target, methodInfo.Name);
}

В OP эту функцию можно вызвать следующим образом, чтобы получить напрямую вызываемый Func <>:

Func<object, int> f = (Func<object, int>)mi.CreateDelegate(null, typeof(object), typeof(int));
f(context);

Спасибо @Sagi (https://stackoverflow.com/a/40579063/2692950) и @ mike.z (https://stackoverflow.com/a/52641599/2692950) за то, что привели меня к этому решению.

Другие вопросы по теме