Как программно создать делегат Func ‹›

У меня есть небольшая структура внедрения зависимостей, и я пытаюсь заставить ее разрешать Lazy<> экземпляры динамически. Идея в том, чтобы сделать что-то вроде этого:

DIContainer.Register<IDbCommand,SqlCommand>();

var lazyCommand = DIContainer.Resolve<Lazy<IDbCommand>>();

На днях я прочитал, что Autofac может это сделать.

Я застрял, пытаясь установить конструктор для этого Lazy<> экземпляра. В следующем тестовом коде создается исключение, потому что конструктор желаемого типа ожидает Func<arg>, но я передаю Func<Object>:

    static readonly Type _lazyType = typeof(Lazy<>);
    static Object ResolveTest(Type type)
    {
        if (type.IsGenericType && type.GetGenericTypeDefinition() == _lazyType)
        {
            var arg = type.GetGenericArguments()[0];

            return Activator.CreateInstance(_lazyType.MakeGenericType(arg), new Func<Object>(() => ResolveType(arg)));
        }
        else 
            return ResolveType(type);
    }

У меня нет идей о том, как создать делегат, который подходит для параметра конструктора Lazy<>. Любая идея?

Ваше здоровье.


person vtortola    schedule 23.10.2013    source источник
comment
Это непросто, но вы сможете создать любой общий тип таким же образом. msdn.microsoft.com/en-us/library/b8ytshk6.aspx может подтолкнуть вас в правильном направлении.   -  person Michael J. Gray    schedule 23.10.2013


Ответы (3)


Это нетривиально. Одно из возможных решений - работать с отражением:

  1. Создайте общий ResolveType метод:

    public static T ResolveType<T>()
    {
        return (T)ResolveType(typeof(T));
    }
    
  2. Создайте делегата, который использует этот метод:

    // You probably want to cache this MethodInfo:
    var method = typeof(TypeContainingResolveType)
                     .GetMethods()
                     .Single(x => x.IsGenericMethod && 
                                  x.Name == "ResolveType")
                     .MakeGenericMethod(arg);
    
    var delegate = Delegate.CreateDelegate(
                       typeof(Func<>).MakeGenericType(arg),
                       method);
    
  3. Используйте этого делегата:

    return Activator.CreateInstance(_lazyType.MakeGenericType(arg), delegate);
    
person Daniel Hilgarth    schedule 23.10.2013
comment
Вы забыли method.MakeGenericMethod(arg). - person Eli Arbel; 23.10.2013
comment
+1 для Вас Даниэль за этот хороший ответ. vtortola проверьте это все очень красиво @JonSkeet stackoverflow.com/questions/773099/ - person Bassam Alugili; 23.10.2013
comment
Это не работает: невозможно выполнить привязку к целевому методу, поскольку его подпись или прозрачность безопасности несовместимы с сигнатурой типа делегата. - person vtortola; 23.10.2013
comment
@BassamAlugili - это не только создание Func ‹›, но и логика, которая должна запускаться в ней при вызове. - person vtortola; 23.10.2013
comment
@EliArbel: Верно. Забыл скопировать из тестового проекта. Фиксированный - person Daniel Hilgarth; 23.10.2013

Это приложение выводит «Истина» и «0». Т.е. ResolveTest(typeof(Lazy<int>)) возвращает объект Lazy<int>, созданный так, как вы хотели.

using System;
using System.Linq.Expressions;

namespace TestApp
{
    public class Class1
    {
        public static void Main()
        {
            object lazyInt = ResolveTest(typeof(Lazy<int>));
            Console.WriteLine(lazyInt.GetType() == typeof(Lazy<int>));
            Console.WriteLine(((Lazy<int>)lazyInt).Value);
        }

        static readonly Type _lazyType = typeof(Lazy<>);
        static Object ResolveTest(Type type)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == _lazyType)
            {
                var arg = type.GetGenericArguments()[0];
                var lazyArgType = _lazyType.MakeGenericType(arg);
                var funcArgType = typeof(Func<>).MakeGenericType(arg);
                var funcCtor = lazyArgType.GetConstructor(new[] { funcArgType });
                Expression<Func<object>> f = () => ResolveTest(arg);
                var func = typeof(Class1).GetMethod("BuildCastedThing").MakeGenericMethod(arg).Invoke(null, new[] { f });
                var arguments = new object[] { func };

                var retVal = funcCtor.Invoke(arguments);
                return retVal;
            }
            else
                return ResolveType(type);
        }
        public static object ResolveType(Type type)
        {
            return Activator.CreateInstance(type);
        }
        public static Func<T> BuildCastedThing<T>(Expression<Func<object>> f)
        {
            Expression<Func<T>> expr =
                Expression.Lambda<Func<T>>(
                    Expression.Convert(
                        Expression.Invoke(f),
                        typeof(T)));

            return expr.Compile();
        }
    }
}

Это способ переписать ResolveTest как общий Resolve<T> (например, Resolve<int> возвращает Lazy<int>). Это немного отличается, поскольку нет эквивалента ResolveTest(typeof(int)), который возвращает int.

static Lazy<T> Resolve<T>()
{
    var arg = typeof(T);
    return new Lazy<T>(() => (T)ResolveType(arg));
}

Или с общим ResolveType<T>:

static Lazy<T> Resolve<T>()
{
    return new Lazy<T>(() => ResolveType<T>());
}
public static T ResolveType<T>()
{
    return Activator.CreateInstance<T>();
}
person Tim S.    schedule 23.10.2013
comment
Думаю, в выражениях нет необходимости, но это интересно. Спасибо! - person vtortola; 23.10.2013
comment
@vtortola Я не мог найти лучшего способа привести возвращаемое значение object ResolveType(Type) к типу. - person Tim S.; 23.10.2013

person    schedule
comment
Мне нравится, что вам не нужен статический вспомогательный метод :) - person vtortola; 24.10.2013