Быстрое создание объектов вместо 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);
После построения делегата сохраните его где-нибудь в статическом словаре или в статическом поле класса с общим параметром. Не создавайте новый делегат каждый раз. Используйте один делегат для построения нескольких экземпляров заданного типа.