Быстрое создание объектов вместо Activator.CreateInstance(type)

Я пытаюсь улучшить производительность нашего приложения. У нас много вызовов Activator.CreateInstance, которые вызывают некоторое горе.

Мы создаем множество классов на основе интерфейса (ITabDocument), и после осмотра я подумал об использовании этого кода:

Код не лучше (минимально медленнее), чем с использованием кода Activator.CreateInstance, который у нас был.

    public static Func<T> CreateInstance<T>(Type objType) where T : class, new()
    {
        var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, objType);
        ILGenerator ilGen = dynMethod.GetILGenerator();
        ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(Type.EmptyTypes));
        ilGen.Emit(OpCodes.Ret);
        return (Func<T>)dynMethod.CreateDelegate(typeof(Func<T>));
    }

Мне интересно, почему это так, все, что я делаю, это:

ITabDocument document = CreateInstance<ITabDocument>(Type.GetType("[Company].Something"));

Есть ли лучший способ создания объектов, которые помогут в этом? Это немного сложно, когда вы не уверены в конкретном типе.

Ответ 1

Я провел несколько этапов сравнения (я бы записал минимальные детали):

public static T Instance() //~1800 ms
{
    return new T();
}

public static T Instance() //~1800 ms
{
    return new Activator.CreateInstance<T>();
}

public static readonly Func<T> Instance = () => new T(); //~1800 ms

public static readonly Func<T> Instance = () => 
                                 Activator.CreateInstance<T>(); //~1800 ms

//works for types with no default constructor as well
public static readonly Func<T> Instance = () => 
               (T)FormatterServices.GetUninitializedObject(typeof(T)); //~2000 ms


public static readonly Func<T> Instance = 
     Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();  
     //~50 ms for classes and ~100 ms for structs

Как говорит CD, скомпилированное выражение является самым быстрым и с большим отрывом. Все методы, кроме (T)FormatterServices.GetUninitializedObject(typeof(T)) , работают только для типов с конструктором по умолчанию.

И кэширование скомпилированного результирующего делегата тривиально, если у вас есть статический класс для общего типа. Как:

public static class New<T> where T : new()
{
    public static readonly Func<T> Instance = Expression.Lambda<Func<T>>
                                              (
                                               Expression.New(typeof(T))
                                              ).Compile();
}

Обратите внимание на ограничение new. Вызвать что-нибудь

MyType me = New<MyType>.Instance();

За исключением того, что первый класс загружается в память, выполнение будет самым быстрым.

Чтобы иметь класс, который обрабатывает оба типа со стандартным конструктором и без него, я взял гибридный подход отсюда:

public static class New<T>
{
    public static readonly Func<T> Instance = Creator();

    static Func<T> Creator()
    {
        Type t = typeof(T);
        if (t == typeof(string))
            return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile();

        if (t.HasDefaultConstructor())
            return Expression.Lambda<Func<T>>(Expression.New(t)).Compile();

        return () => (T)FormatterServices.GetUninitializedObject(t);
    }
}

public static bool HasDefaultConstructor(this Type t)
{
    return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
}

Эффективно будет обрабатывать типы значений.

Обратите внимание, что (T)FormatterServices.GetUninitializedObject(t) не будет выполнено для string. Следовательно, для обработки пустой строки требуется специальная обработка строки.

Ответ 2

Это может помочь: Не используйте Activator.CreateInstance или ConstructorInfo.Invoke, используйте скомпилированные лямбда-выражения:

// Make a NewExpression that calls the ctor with the args we just created
NewExpression newExp = Expression.New(ctor, argsExp);                  

// Create a lambda with the New expression as body and our param object[] as arg
LambdaExpression lambda = Expression.Lambda(typeof(ObjectActivator), newExp, param);            


// Compile it
ObjectActivator compiled = (ObjectActivator)lambda.Compile();

Ответ 3

Проблема в том, что если вы собираетесь вызывать CreateInstance снова и снова напрямую, а не сохранять результат где-то и использовать этот результат снова и снова, вам, вероятно, следует просто пойти дальше и выполнить кеширование внутри него.

internal static class DelegateStore<T> {
     internal static IDictionary<string, Func<T>> Store = new ConcurrentDictionary<string,Func<T>>();
}

public static T CreateInstance<T>(Type objType) where T : class
{
    Func<T> returnFunc;
    if(!DelegateStore<T>.Store.TryGetValue(objType.FullName, out returnFunc)) {
        var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, objType);
        ILGenerator ilGen = dynMethod.GetILGenerator();
        ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(Type.EmptyTypes));
        ilGen.Emit(OpCodes.Ret);
        returnFunc = (Func<T>)dynMethod.CreateDelegate(typeof(Func<T>));
        DelegateStore<T>.Store[objType.FullName] = returnFunc;
    }
    return returnFunc();
}

Ответ 4

Вероятно, вы получаете некоторые издержки от генерации одного и того же кода.

ILGenerator динамически создает код для factory.

Создайте somekind карты или Dictionary типов, которые вы уже использовали, и сохраните метод factory, созданный для этого типа.

Ответ 5

Общий метод построения делегатов, вызывающий конструктор напрямую. Автоматически ищет конструктор в заданном типе с сигнатурой данного типа делегата и создает делегат этого типа. Код здесь:

/// <summary>
/// Reflective object construction helper.
/// All methods are thread safe.
/// </summary>
public static class Constructor
{
    /// <summary>
    /// Searches an instanceType constructor with delegateType-matching signature and constructs delegate of delegateType creating new instance of instanceType.
    /// Instance is casted to delegateTypes return type. 
    /// Delegate return type must be assignable from instanceType.
    /// </summary>
    /// <param name="delegateType">Type of delegate, with constructor-corresponding signature to be constructed.</param>
    /// <param name="instanceType">Type of instance to be constructed.</param>
    /// <returns>Delegate of delegateType wich constructs instance of instanceType by calling corresponding instanceType constructor.</returns>
    public static Delegate Compile(Type delegateType,Type instanceType)
    {
        if (!typeof(Delegate).IsAssignableFrom(delegateType))
        {
            throw new ArgumentException(String.Format("{0} is not a Delegate type.",delegateType.FullName),"delegateType");
        }
        var invoke = delegateType.GetMethod("Invoke");
        var parameterTypes = invoke.GetParameters().Select(pi => pi.ParameterType).ToArray();
        var resultType = invoke.ReturnType;
        if(!resultType.IsAssignableFrom(instanceType))
        {
            throw new ArgumentException(String.Format("Delegate return type ({0}) is not assignable from {1}.",resultType.FullName,instanceType.FullName));
        }
        var ctor = instanceType.GetConstructor(
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null);
        if(ctor == null)
        {
            throw new ArgumentException("Can't find constructor with delegate signature","instanceType");
        }
        var parapeters = parameterTypes.Select(Expression.Parameter).ToArray();

        var newExpression = Expression.Lambda(delegateType,
            Expression.Convert(Expression.New(ctor, parapeters), resultType),
            parapeters);
        var @delegate = newExpression.Compile();
        return @delegate;
    }
    public static TDelegate Compile<TDelegate>(Type instanceType)
    {
        return (TDelegate) (object) Compile(typeof (TDelegate), instanceType);
    }
}

является частью Yappi источников проекта. Используя его, вы можете построить делегат, вызывающий любой конструктор данного типа, включая конструктор с параметрами (кроме параметров ref и out).

Использование образца:

var newList = Constructor.Compile<Func<int, IList<String>>>(typeof (List<String>));
var list = newList(100);

После построения делегата сохраните его где-нибудь в статическом словаре или в статическом поле класса с общим параметром. Не создавайте новый делегат каждый раз. Используйте один делегат для построения нескольких экземпляров заданного типа.