C #: функция с конструктором унаследованного типа

#c# #lambda #constructor #func #inherited

#c# #лямбда #конструктор #функция #унаследованный

Вопрос:

Как мы знаем, вы можете указать на конструктор как Func<T> вот так:

 Func<MyObject> constructor = () => new MyObject();
var newObject = constructor();
  

Но есть ли способ создать конструктор для объекта, который, как вы знаете, наследуется от MyObject, но вы не знаете его точный тип?

 Type inheritedObjectType = obj; // Parameter
Func<MyObject> constructor = () => new MyObject(); // as inheritedObjectType
var newInheritedObject = constructor; // Should now be of the inherited type
  

Ответ с использованием Activator или чего-либо возвращающего Object — это не вариант.

Редактировать: Я не знаю, к какому типу относится производный тип во время компиляции. У меня есть только System.Type .

Ответ №1:

Вы можете использовать деревья выражений для построения и компиляции делегата, который создаст ваш производный тип:

 Func<TBase> CreateDelegate<TBase>(Type derived)
{
    var ctor = derived.GetConstructor(Type.EmptyTypes);

    if (ctor == null)
    {
        throw new ArgumentException("D'oh! No default ctor.");
    }

    var newExpression = Expression.Lambda<Func<TBase>>(
        Expression.New(ctor, new Expression[0]), 
        new ParameterExpression[0]);

    return newExpression.Compile();
}
  

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

 Func<MyBase> create = CreateDelegate<MyBase>(typeof(Derived));

MyBase instance = create();
  

Когда вы кэшируете этот делегат, вы получите максимальную производительность.

Комментарии:

1. кстати: это именно то, что фреймворки внедрения зависимостей сделают для вас. Большинство фреймворков допускают регистрацию следующего синтаксиса: container.Register(typeof(MyBase), derived) и вы можете получить новый экземпляр следующим образом: container.GetInstance<MyBase>() и вуаля.

Ответ №2:

Демонстрирует ли эта программа желаемое поведение?

 class MyObject{}
class Derived : MyObject {}
internal class Program
{

    public static void Main()
    {
        // the type you want to construct
        Type type = typeof (Derived);

        MethodInfo getConstructor = MakeConstructorGetter(type);
        Func<MyObject> constructor = (Func<MyObject>)getConstructor.Invoke(null, null);

        var obj = constructor();
        Console.WriteLine(obj.GetType());
    }

    private static MethodInfo MakeConstructorGetter(Type type)
    {
        MethodInfo mi = typeof(Program).GetMethod("GetObjectConstructor", BindingFlags.Static | BindingFlags.NonPublic);
        var getConstructor = mi.MakeGenericMethod(type);
        return getConstructor;
    }

    private static Func<T> GetObjectConstructor<T>() where T : MyObject, new()
    {
        return () => new T();
    }
}
  

MakeConstructorGetter выдаст исключение, если тип, описанный Type , не соответствует общим ограничениям. Итак, это приведет к сбою во время выполнения, а не во время компиляции, но я предполагаю, что это компромисс, который вам нужно сделать, когда вы имеете дело с обобщениями динамически, подобными этому.

Комментарии:

1. Я не знаю, что такое «Производный», кроме System.Type.

2. @Seb: Обновил ответ, чтобы он создавал универсальный метод для данного Type .

3. Хм, «getConstructor. Invoke» в основном похож на Activator. CreateInstance и возвращает объект. Я хочу сослаться на constructor-lambda для повышения производительности, которой эквивалент на основе отражения не совсем обладает. Если это невозможно сделать, то это правильный ответ, и ваше решение — самое близкое, которое можно получить.

4. @Seb: обратите внимание, что getConstructor.Invoke возвращает универсальный конструктор, но не создает объект ; объект создается сконструированным универсальным вызовом (поэтому там не должно быть отражения). Вы должны легко иметь возможность кэшировать сгенерированные конструкторы для каждого типа.

5. Это действительно работает? Я не думаю, что getConstructor.Invoke(null, null) возвращает Func<T> , а просто что-то, что наследуется от MyObject .