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 и DelegateWrapper, но эту идею легко расширить, чтобы реализовать любое разумное количество аргументов. Требуется реализовать соответствующие методы Create() и Invoke() в базовом классе и переопределить только один подходящий Invoke().

Его можно использовать следующим образом:

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 и вызывать при необходимости.

Надеюсь, поможет. Люк

Можно ли ограничить универсальный метод работой только со ссылочными типами, а также создать другой, который будет работать только с типами значений, и решить, какие функции использовать соответствующим образом?

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