CreateDelegate с неизвестными типами
Я пытаюсь создать делегат для чтения / записи свойств неизвестного типа класса во время выполнения.
У меня есть общий класс Main<T>
и метод, который выглядит так:
Delegate.CreateDelegate(typeof(Func<T, object>), get)
где get
это MethodInfo
собственности, которая должна быть прочитана. Проблема в том, что когда свойство возвращается int
(Я полагаю, что это происходит для типов значений) вышеприведенный код генерирует ArgumentException, потому что метод не может быть привязан. В случае строки это работает хорошо.
Чтобы решить эту проблему, я изменил код так, чтобы соответствующий тип делегата генерировался с помощью MakeGenericType
, Итак, теперь код:
Type func = typeof(Func<,>);
Type generic = func.MakeGenericType(typeof(T), get.ReturnType);
var result = Delegate.CreateDelegate(generic, get)
Проблема в том, что созданный экземпляр делегата generic
поэтому я должен использовать DynamicInvoke
который будет таким же медленным, как использование чистого отражения для чтения поля.
Поэтому мой вопрос заключается в том, почему первый фрагмент кода не работает с типами значений. Согласно MSDN, он должен работать так, как говорится, что
Возвращаемый тип делегата совместим с возвращаемым типом метода, если возвращаемый тип метода является более строгим, чем возвращаемый тип делегата.
и как выполнить делегат во втором фрагменте, чтобы он был быстрее, чем отражение.
Благодарю.
4 ответа
Вот один из способов решить вашу проблему. Создайте универсальный метод:
public static Func<T, object> MakeDelegate<U>(MethodInfo @get)
{
var f = (Func<T, U>)Delegate.CreateDelegate(typeof(Func<T, U>), @get);
return t => f(t);
}
Таким образом, компилятор C# заботится о вставке необходимого бокса (если есть) для преобразования f(t)
(типа U
) чтобы object
, Теперь вы можете использовать отражение, чтобы вызвать это MakeDelegate
метод с U
установлен в @get.ReturnType
и что вы получите обратно будет Func<T, object>
который можно вызвать, не прибегая к использованию DynamicInvoke
,
Ваш оригинальный код может работать только для ссылочных типов. Вот почему строка не была проблемой, она напрямую связана с System.Object. То, что тип значения является производным от ValueType и Object, является хорошей иллюзией на бумаге, но фактически требует кода. Компилятор C# автоматически испускает этот код, он требует преобразования в бокс. Это та часть, которая здесь отсутствует, нет преобразования времени выполнения из int в объект без кода операции BOX.
Вы можете получить этот код операции в своем коде, но вам придется использовать System.Reflection.Emit.
Прежде чем идти туда, сначала проверьте, действительно ли то, что у вас есть, слишком медленное. За счет размышлений копается метаданные из сборки. Это было сделано при создании делегата, информация о типе кешируется после этого.
Я пришел к подобному подходу @kvb, но с классами-оболочками. Они работают немного быстрее, потому что вместо прокси-действий используются виртуальные методы. Также прокси-действие делает захват контекста невидимым DisplayClass. Это моя версия.
using System;
using System.Reflection;
public abstract class DelegateWrapper
{
public static DelegateWrapper Create ( object target, MethodInfo method, Type arg ) =>
Create( target, method, typeof( DelegateWrapper<> ), arg );
public static DelegateWrapper Create ( object target, MethodInfo method, Type arg1, Type arg2 ) =>
Create( target, method, typeof( DelegateWrapper<,> ), arg1, arg2 );
private static DelegateWrapper Create ( object target, MethodInfo method, Type generic, params Type[] argTypes )
{
var wrapperType = generic.MakeGenericType( argTypes );
var ctor = wrapperType.GetConstructor( new Type[] { typeof( object ), typeof( MethodInfo ) } );
var wrapper = ctor.Invoke( new object[] { target, method } ) as DelegateWrapper;
return wrapper;
}
public virtual void Invoke ( object arg )
{
throw new NotSupportedException();
}
public virtual void Invoke ( object arg1, object arg2 )
{
throw new NotSupportedException();
}
}
public class DelegateWrapper<T> : DelegateWrapper
{
private Action<T> _delegate;
public DelegateWrapper ( object target, MethodInfo method )
{
_delegate = ( Action<T> )Delegate.CreateDelegate( typeof( Action<T> ), target, method );
}
public override void Invoke ( object arg )
{
_delegate.Invoke( ( T )arg );
}
}
public class DelegateWrapper<T1, T2> : DelegateWrapper
{
private Action<T1, T2> _delegate;
public DelegateWrapper ( object target, MethodInfo method )
{
_delegate = ( Action<T1, T2> )Delegate.CreateDelegate( typeof( Action<T1, T2> ), target, method );
}
public override void Invoke ( object arg1, object arg2 )
{
_delegate.Invoke( ( T1 )arg1, ( T2 )arg2 );
}
}
Я реализовал DelegateWrapper
Его можно использовать следующим образом:
object target;
var targetType = target.GetType();
var methodInfo = targetType.GetMethod("MethodWith2Strings");
var delegateWrapper = DelegateWrapper.Create(target, methodInfo, typeof(string), typeof(string));
delegateWrapper.Invoke("String 1", "String 2");
Вы вызываете неудачу, потому что Вы требуете объекта не типа значения (как INT) - очевидно, Func<T, int>
это не Func<T, Int>
- это не будет работать ни с одним vt, как double или bool. Либо верните упакованный Int (или любой другой vt, который у вас есть) . или (возможно, лучше) использовать API-интерфейс отражения.
Используя классы отражения, вы можете создавать динамические методы и сохранять их как делегаты, или создавать динамические делегаты и сохранять их в некоторых структурах. Вы можете сделать это только один раз (возможно, один раз за время выполнения), сохранить его в некотором Dict и вызывать при необходимости.
Надеюсь, поможет. Люк
Можно ли ограничить универсальный метод работой только со ссылочными типами, а также создать другой, который будет работать только с типами значений, и решить, какие функции использовать соответствующим образом?