Accueil > .Net, C#, Développement > [C#] Construire un objet

[C#] Construire un objet

Pour construire un objet, il y a toujours le fameux :

StringBuilder str = new StringBuilder();

Mais qu’en est-il lors que l’on ne connait pas le type de l’objet ?
C’est à dire que l’on doive créer un objet à la volée ?

Dans ce billet, nous allons voir quelques méthodes pour ce faire.

Prenons l’objet simpliste suivant :

public class TestObject
{
    public string Name { get; set; }
    public TestObject()
    {
        this.Name = "Default";
    }
    public TestObject(string name)
    {
        this.Name = name;
    }
}

 
Déjà, commençons par le cas et la méthode la plus simple : création d’un objet à partir du constructeur (public) par défaut.

Pour cela, il suffira de la méthode suivante :

public static T Create<T>()
    where T : new()
{
    return new T();
}

// Utilisation :
TestObject obj = Create<TestObject>();

Ici, il y a une restriction sur le type générique T indiquant qu’il doit avoir, à minima, un constructeur par défaut ET que ce constructeur DOIT être publique.
De plus, à noter le « new T() » et non pas « default(T) » car default va prendre la valeur par défaut qui est, pour une classe…null. Donc ça ne nous servira à rien.

Cas simple, donc.
Mais qu’en est-il si l’on veut appeler le second constructeur ?
On peut utiliser la reflection.

public static T Create<T>(params object[] args)
{
    Type[] types =
        (args != null && args.Count() > 0)
        ? args.Select(p => p.GetType()).ToArray()
        : new Type[0];

    ConstructorInfo ctor = typeof(T).GetConstructor(
        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public,
        null,
        types,
        null);

    return (T)ctor.Invoke(args);
}

// Utilisations :
TestObject obj = Create<TestObject>("Custom");
List<string> list = Create<List<string>>(10);
StringBuilder str = Create<StringBuilder>("Content", 0, 7, 10);

Ici, c’est déjà un peu plus complexe (mais pas trop).
Comme je veux pouvoir utiliser la même méthode pour n’importe quel nombre de paramètres, j’utilise le mot-clef « params » sur un tableau d’objet. Je peux donc envoyer du string, du int, de l’IEnumerable…

Ensuite, à partir des paramètres passés, je récupère leurs types pour former un tableau (de types, donc).
Grâce à ce tableau, je peux récupérer le constructeur qui va bien.
Et enfin, dernière étape, invoquer le constructeur, le caster dans le type attendu et le renvoyer.

Et si on mettait un peu plus de Linq pour éviter le cast ?
C’est possible !

public static T Create<T>(Type type, params object[] args)
{
    Type[] types = 
        (args != null && args.Count() > 0)
        ? args.Select(p => p.GetType()).ToArray()
        : new Type[0];

    ConstructorInfo ctor = type.GetConstructor(
        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public,
        null,
        types,
        null);

    Expression[] parameters = null;
    if (args != null && args.Count() > 0)
    {
        parameters = new Expression[args.Length];
        for(int index = 0; index < args.Length; index++)
        {
            parameters[index] = Expression.Constant(
                        args[index],
                        args[index].GetType());
            // Note : le second paramètre est optionnel.
        }
    }

    Expression exp = Expression.New(ctor, parameters);
    Expression<Func<T>> lambda = Expression.Lambda<Func<T>>(exp);
    Func<T> compiled = lambda.Compile();
    return compiled();
}

Les deux premières étapes sont identiques, donc je n’y reviens pas.
Par contre, les deux derniers sont un brin plus complexes.

Le if est un peu obvious : s’il y a des arguments, on les traite.
Le for, lui, va se charger de créer les expressions symbolisant chaque arguments à passer au constructeur.
Expression.New porte assez bien son nom puisque c’est elle qui va créer l’appel au constructeur.
Mais pour appeler l’expression, il faut créer une lambda, la compiler puis l’exécuter (trois dernières lignes).

En usage, on aura donc :

TestObject obj3 = Create<TestObject>(typeof(TestObject), "Objet custom");
TestObject obj4 = Create<TestObject>(typeof(TestObject));
StringBuilder str2 = Create<StringBuilder>(typeof(StringBuilder), "Content", 0, 7, 10);
StringBuilder str3 = Create<StringBuilder>(typeof(StringBuilder));
IEnumerable<string> list2 = Create<IEnumerable<string>>(typeof(List<string>), 10);

Et là, ce qui est important, c’est surtout le dernier exemple : on va pouvoir créer à la volée des objets en se basant uniquement sur la classe abstraite et en indiquant quelle classe concrète doit être utilisée.

Il est possible d’utiliser cette méthode pour manipuler des classes abstraites construites à partir d’un simple namespace.
Exemple :

string classNamespace = GetFromConfig();
Type type = Extends.GetType(Assembly.GetExecutingAssembly(), classNamespace);
AbstractClass absClass = Create<AbstractClass>(type);

Cependant, en terme de performance, c’est réellement pas terrible.

Pour les tester, j’ai simplement fait comme ceci :

public static TimeSpan Benckmark(Action action, int loops = 10000)
{
    Stopwatch s1 = Stopwatch.StartNew();
    for (int index = 0; index < loops; index++)
    {
        action();
    }
    s1.Stop();
    return s1.Elapsed;
}

// Utilisation
Console.WriteLine("Ctor : {0}", Benckmark(() =>
{
    new TestObject();
}));
Console.WriteLine("Activator : {0}", Benckmark(() =>
{
    Activator.CreateInstance(typeof(TestObject));
}));
Console.WriteLine("Create<T> : {0}", Benckmark(() =>
{
    ctr.Create<TestObject>(typeof(TestObject));
}));
Console.WriteLine("Create<T>(args) : {0}", Benckmark(() =>
{
    ctr.Create<TestObject>("Objet custom");
}));

Ce qui donne :

Ctor : 00:00:00.0003794
Activator : 00:00:00.0016285
Create<T> : 00:00:00.9773437
Create<T>(args) : 00:00:00.9704380

Et là, c’est un peu le drame, quand même.
Mais…on peut faire mieux !

Je vous redirige donc vers ce billet : Creating objects (ainsi que les liens données dans le billet comme dans les commentaires).

En utilisant, j’ai donc :

ConstructorInfo ctor = typeof(TestObject).GetConstructors().First();
ObjectActivator<TestObject> createdActivator = GetActivator<TestObject>(ctor);

Console.WriteLine("ObjectActivator<T>(args) : {0}", Benckmark(() =>
{
    createdActivator("Objet custom");
}));

En faisant retourner le tout, j’obtiens donc :

Ctor : 00:00:00.0002483
Activator : 00:00:00.0014881
Create<T> : 00:00:00.9872635
Create<T>(args) : 00:00:00.9704824
ObjectActivator<T>(args) : 00:00:00.0004803

Côté performance, c’est quand même beaucoup mieux, même si c’est le double du constructeur de base…
Mais il faut avouer que lorsque l’on veut construire un objet dont on ignore le type…c’est quand même un peu plus dur !🙂

Et avec une modification vraiment mineure :

public static ObjectActivator<T> GetActivator<T>(ConstructorInfo ctor, Type type)
{
    ParameterInfo[] paramsInfo = ctor.GetParameters();

    //create a single param of type object[]
    ParameterExpression param =
        Expression.Parameter(typeof(object[]), "args");

    Expression[] argsExp =
        new Expression[paramsInfo.Length];

    //pick each arg from the params array 
    //and create a typed expression of them
    for (int i = 0; i < paramsInfo.Length; i++)
    {
        Expression index = Expression.Constant(i);
        Type paramType = paramsInfo[i].ParameterType;

        Expression paramAccessorExp =
            Expression.ArrayIndex(param, index);

        Expression paramCastExp =
            Expression.Convert(paramAccessorExp, paramType);

        argsExp[i] = paramCastExp;
    }

    //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<T>), newExp, param);

    //compile it
    ObjectActivator<T> compiled = (ObjectActivator<T>)lambda.Compile();
    return compiled;
}

On peut avoir :

public abstract class BaseClass
{
    public string Name { get; set; }
    protected BaseClass(string value)
    {
        this.Name = String.Format("BaseClass : {0}", value);
    }
}
public class DerivedClass : BaseClass
{
    public string DerivedName { get; set; }

    public DerivedClass(string value)
        : base(value)
    {
        this.DerivedName = String.Format("DerivedClass : {0}", value);
    }
}

Type type = typeof(DerivedClass);
ConstructorInfo ctor = type.GetConstructors().First();
ObjectActivator<DerivedClass> createdActivator = GetActivator<DerivedClass>(ctor, type);
BaseClass obj = createdActivator("text");

Que du bonheur, donc.

Catégories :.Net, C#, Développement
  1. Aucun commentaire pour l’instant.
  1. 16/09/2013 à 08:14

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :