Рассмотрим следующий код:
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).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. Есть гораздо более простые способы разобраться с моим конкретным примером. Меня больше интересует общий случай. Я понимаю, что, скорее всего, прошу чего-то, чего не существует, но попробовать не помешает :)
Я не думаю, что есть способ. Вы уже (я думаю) осознали, что объявить переменную не может удерживать то, что вы пытаетесь создать. Следующим шагом будет создание некой функции-оболочки, которая может вызывать желаемую функцию, но сама по себе не должна передавать InternalPrivateClass. Что вы можете хранить какой-то Func, готовый его вызвать. Но выполнение этой функции сталкивается со всеми теми же препятствиями.
Я добавил параграф, показывающий, как частный метод, который ожидает частный тип в качестве параметра, вызывается без прямой ссылки на частный тип. Похоже, что решение этой проблемы, если оно существует, действительно включало бы испускание IL. К сожалению, я недостаточно знаю об этой области.
Является ли частный класс, который вам нужно создать, таким же простым, как в приведенном вами примере? т.е. общедоступный конструктор без параметров? Это реальный код, который вы хотите скомпилировать (если мы проигнорируем частные модификаторы): var x = thirdPartyInstance.SomeFunc(new ThirdPartyClass.InternalPrivateClass());? Я спрашиваю, поскольку у меня есть некоторый опыт работы с излучением IL, я мог бы попытаться вместе сделать небольшую попытку сделать именно это.
На самом деле я никогда не создаю экземпляр InternalPrivateClass. Скорее, я сохраняю ссылку на него, используя его базовый класс. Вот более простая версия примера кода: stackoverflow.com/a/52640221/2692950. Там я в значительной степени ищу способ .Invoke делегата напрямую вместо .DynamicInvoke





Это немного сложно сделать, но это можно сделать с помощью 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.0Method | 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. Я даю вам общепринятый ответ за то, что вы показали мне, как это было возможно. Если вы считаете, что мое решение общего случая, приведенное ниже, должно быть помечено как принятое, пожалуйста, дайте мне знать.
Вот общее решение для создания 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) за то, что привели меня к этому решению.
Косвенное решение: сторонняя библиотека кажется .NET. К счастью, вы можете скопировать исходный код из linksource.microsoft.com/#mscorlib/system/security/…, сделать его скомпилированным (что не обязательно легко), а затем изменить его по мере необходимости.