Création d'une instance de type sans constructeur par défaut en C # à l'aide de la réflexion

97

Prenons l'exemple du cours suivant:

class Sometype
{
    int someValue;

    public Sometype(int someValue)
    {
        this.someValue = someValue;
    }
}

Je souhaite ensuite créer une instance de ce type en utilisant la réflexion:

Type t = typeof(Sometype);
object o = Activator.CreateInstance(t);

Normalement, cela fonctionnera, mais comme SomeTypen'a pas défini de constructeur sans paramètre, l'appel à Activator.CreateInstancelèvera une exception de type MissingMethodExceptionavec le message " Aucun constructeur sans paramètre défini pour cet objet. " Existe-t-il un autre moyen de créer une instance de ce type? Ce serait un peu nul d'ajouter des constructeurs sans paramètre à toutes mes classes.

Aistina
la source
2
FormatterServices.GetUninitializedObjectne permet pas de créer une chaîne non initialisée. Vous pouvez obtenir une exception: System.ArgumentException: Uninitialized Strings cannot be created.veuillez garder cela à l'esprit.
Bartosz Pierzchlewicz
Merci pour la mise en garde, mais je gère déjà les chaînes et les types de base séparément.
Aistina

Réponses:

142

J'ai initialement publié cette réponse ici , mais voici une réimpression car ce n'est pas exactement la même question mais a la même réponse:

FormatterServices.GetUninitializedObject()créera une instance sans appeler de constructeur. J'ai trouvé cette classe en utilisant Reflector et en explorant certaines des classes de sérialisation .Net.

Je l'ai testé à l'aide de l'exemple de code ci-dessous et il semble que cela fonctionne très bien:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.Serialization;

namespace NoConstructorThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass)); //does not call ctor
            myClass.One = 1;
            Console.WriteLine(myClass.One); //write "1"
            Console.ReadKey();
        }
    }

    public class MyClass
    {
        public MyClass()
        {
            Console.WriteLine("MyClass ctor called.");
        }

        public int One
        {
            get;
            set;
        }
    }
}
Jason Jackson
la source
Génial, on dirait que c'est exactement ce dont j'ai besoin. Je suppose que non initialisé signifie que toute sa mémoire sera définie sur des zéros? (Similaire à la façon dont les structures sont instanciées)
Aistina
La valeur par défaut de chaque type sera la valeur par défaut. Ainsi, les objets seront nuls, ints 0, etc. Je pense que toute initialisation au niveau de la classe se produit, mais aucun constructeur n'est exécuté.
Jason Jackson
14
@JSBangs, ça craint que vous donniez une réponse parfaitement légitime. Votre commentaire et l'autre réponse ne répondent pas réellement à la question posée. Si vous pensez avoir une meilleure réponse, donnez-en une. Mais la réponse que j'ai fournie met en évidence comment utiliser une classe documentée de la même manière que d'autres classes de sérialisation utilisent ce code.
Jason Jackson
21
@JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) n'est pas non documenté.
Autodidacte du
72

Utilisez cette surcharge de la méthode CreateInstance:

public static Object CreateInstance(
    Type type,
    params Object[] args
)

Crée une instance du type spécifié à l'aide du constructeur qui correspond le mieux aux paramètres spécifiés.

Voir: http://msdn.microsoft.com/en-us/library/wcxyzt4d.aspx

pseudo
la source
1
Cette solution simplifie à l'extrême le problème. Que faire si je ne connais pas mon type et que je dis "simplement créer un objet du type dans cette variable Type"?
kamii
23

Lorsque j'en ai évalué les performances, (T)FormatterServices.GetUninitializedObject(typeof(T))c'était plus lent. En même temps, les expressions compilées vous apporteraient de grandes améliorations de vitesse bien qu'elles ne fonctionnent que pour les types avec le constructeur par défaut. J'ai adopté une approche hybride:

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

Cela signifie que l'expression de création est effectivement mise en cache et n'encourt des pénalités que la première fois que le type est chargé. Traitera également les types de valeur de manière efficace.

Appeler:

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

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

nawfal
la source
1
Il est étrange de voir comment un regard sur une ligne de code de quelqu'un peut sauver une journée. Merci Monsieur! Les raisons de performance m'ont amené à votre message et le truc est fait :) FormatterServices et les classes Activator sont sous-performantes par rapport aux expressions compilées, quel dommage que l'on trouve des activateurs partout.
jmodrak
@nawfal En ce qui concerne votre gestion spéciale pour la chaîne, je sais qu'elle échouerait pour la chaîne sans cette gestion spéciale, mais je veux juste savoir: fonctionnera-t-elle pour tous les autres types?
Sнаđошƒаӽ
@ Sнаđошƒаӽ malheureusement non. L'exemple donné est barebones et .NET a de nombreux types de types différents. Par exemple, considérez, si vous passez un type de délégué, comment allez-vous lui donner une instance? Ou bien si le constructeur lance que pouvez-vous faire à ce sujet? De nombreuses façons différentes de le gérer. J'ai depuis répondu à cette mise à jour pour gérer beaucoup plus de scénarios dans ma bibliothèque. Il n'est publié nulle part pour le moment.
nawfal
4

Bonnes réponses mais inutilisables sur le framework compact dot net. Voici une solution qui fonctionnera sur CF.Net ...

class Test
{
    int _myInt;

    public Test(int myInt)
    {
        _myInt = myInt;
    }

    public override string ToString()
    {
        return "My int = " + _myInt.ToString();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var ctor = typeof(Test).GetConstructor(new Type[] { typeof(int) });
        var obj = ctor.Invoke(new object[] { 10 });
        Console.WriteLine(obj);
    }
}
Autocrate
la source
1
C'est ainsi que j'appellerais un constructeur non par défaut. Je ne suis pas sûr de vouloir créer un objet sans appeler aucun constructeur du tout.
Rory MacLeod
2
Vous souhaiterez peut-être créer un objet sans appeler de constructeurs si vous écrivez des sérialiseurs personnalisés.
Autodidacte
1
Oui, c'est le scénario d'utilisation exact pour
lequel
1
@Aistina Peut-être pourriez-vous ajouter cette information à la question? La plupart des gens seraient contre la création d'objets sans appeler leurs cteurs et prendraient le temps de discuter avec vous à ce sujet, mais votre cas d'utilisation le justifie en fait, donc je pense que c'est très pertinent pour la question elle-même.
julealgon