web-dev-qa-db-fra.com

Création rapide d'objets au lieu d'Activator.CreateInstance (type)

J'essaie d'améliorer les performances de notre application. Nous avons beaucoup d'appels Activator.CreateInstance qui causent du chagrin.

Nous instancions beaucoup de classes basées sur une interface (ITabDocument) et après avoir regardé autour, j'ai pensé à utiliser ce code:

Le code n'est pas meilleur (en fait légèrement plus lent) que d'utiliser le code Activator.CreateInstance que nous avions.

    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>));
    }

Je me demande pourquoi c'est, tout ce que je fais c'est:

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

Y a-t-il une meilleure façon de créer des objets qui aideraient avec ce qui précède? C'est un peu dur quand vous n'êtes pas sûr du type de béton.

34
Tiffany Townsend

J'ai fait un benchmarking entre ceux-ci (j'écrirais le strict minimum):

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

Comme le dit CD, l'expression compilée est la plus rapide et avec une grande marge. Toutes les méthodes sauf (T)FormatterServices.GetUninitializedObject(typeof(T)) ne fonctionne que pour les types avec constructeur par défaut.

Et la mise en cache du délégué résultant compilé est triviale lorsque vous avez une classe statique par type générique. Comme:

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

Notez la contrainte new. Appelez n'importe quoi

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

Sauf pour la première fois que la classe est chargée en mémoire, l'exécution va être plus rapide.

Pour avoir une classe qui gère les deux types avec ou sans constructeur par défaut, j'ai pris une approche hybride, d'ici :

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;
}

Gérera également les types de valeur de manière efficace.

Notez que (T)FormatterServices.GetUninitializedObject(t) échouera pour string. Par conséquent, une gestion spéciale de la chaîne est en place pour renvoyer une chaîne vide.

45
nawfal

Cela pourrait aider: N'utilisez pas Activator.CreateInstance ou ConstructorInfo.Invoke, utilisez des expressions lambda compilées :

// 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();
16
CD..

Le problème est que si vous allez appeler CreateInstance maintes et maintes fois directement plutôt que d'enregistrer le résultat quelque part et d'utiliser ce résultat encore et encore, vous devriez probablement continuer et mettre en cache à l'intérieur.

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();
}
7
jbtule

Vous obtenez probablement des frais généraux de la génération du même code.

ILGenerator crée dynamiquement du code pour la fabrique.

Créez une sorte de carte ou Dictionary des types que vous avez déjà utilisés, et conservez la méthode d'usine créée pour ce type.

3
Yochai Timmer

Méthode générique pour construire des délégués, appelant directement le constructeur. Recherche automatiquement le constructeur dans le type donné avec la signature du type délégué donné et construit le délégué de ce type. Code ici:

/// <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);
    }
}

fait partie des sources du projet Yappi . En l'utilisant, vous pouvez construire un délégué appelant n'importe quel constructeur de type donné, y compris un constructeur avec des paramètres (sauf les paramètres ref et out).

Exemple d'utilisation:

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

Après la construction du délégué, stockez-le quelque part dans un dictionnaire statique ou dans un champ statique de classe avec un paramètre générique. Ne construisez pas de nouveau délégué à chaque fois. Utilisez un délégué pour construire plusieurs instances de type donné.

0
Kelqualyn