Быстрое создание объектов вместо Activator.CreateInstance (тип) - PullRequest
31 голосов
/ 05 июля 2011

Я пытаюсь улучшить производительность нашего приложения.У нас много вызовов 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"));

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

Ответы [ 5 ]

45 голосов
/ 23 апреля 2013

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

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. Следовательно, имеется специальная обработка для возврата пустой строки.

16 голосов
/ 05 июля 2011

Это может помочь: Не используйте 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();
7 голосов
/ 11 июля 2011

Проблема в том, что если вы собираетесь вызывать 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();
}
3 голосов
/ 05 июля 2011

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

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

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

0 голосов
/ 30 июля 2011

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

/// <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's return type. 
    /// Delegate's 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's 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's 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);

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

...