Comment créer une nouvelle instance d'objet à partir d'un type

747

Il se peut que l'on ne connaisse pas toujours la valeur Typed'un objet au moment de la compilation, mais il peut être nécessaire de créer une instance de Type.

Comment obtenir une nouvelle instance d'objet à partir d'un Type?

tags2k
la source

Réponses:

896

La Activatorclasse dans l' Systemespace de noms racine est assez puissante.

Il y a beaucoup de surcharges pour passer des paramètres au constructeur et autres. Consultez la documentation sur:

http://msdn.microsoft.com/en-us/library/system.activator.createinstance.aspx

ou (nouveau chemin)

https://docs.microsoft.com/en-us/dotnet/api/system.activator.createinstance

Voici quelques exemples simples:

ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);

ObjectType instance = (ObjectType)Activator.CreateInstance("MyAssembly","MyNamespace.ObjectType");
Karl Seguin
la source
21
Heureux d'avoir finalement trouvé cela, mais le deuxième appel n'est pas tout à fait exact, il manque une citation et les paramètres inversés devraient être: ObjectType instance = (ObjectType) Activator.CreateInstance ("MyAssembly", "MyNamespace.ObjectType");
kevinc
10
Vous devez appeler «Unwrap ()» pour obtenir le type d'objet réel que vous souhaitez: ConcreteType instance = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Ε Г И І И О
4
Comment ObjectType instancecorrespond la condition de l'OP "On ne sait pas toujours le type d'un objet au moment de la compilation"? : P
Martin Schneider
@ MA-Maddin d'accord alors object instance = Activator.CreateInstance(...);.
BrainSlugs83
1
Tout le monde sait comment faire cela dans .NET Core? La méthode Unwrap n'est pas disponible sur l'objet.
Justin
145
ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);

La Activatorclasse a une variante générique qui rend cela un peu plus facile:

ObjectType instance = Activator.CreateInstance<ObjectType>();
Konrad Rudolph
la source
8
@ Kevin Bien sûr. Une telle opération ne peut pas fonctionner dans un langage typé statique car elle n'a pas de sens. Vous ne pouvez pas appeler de méthodes sur un objet de type inconnu. Dans l'intervalle (= écriture de cette réponse) C # a obtenu la dynamicconstruction qui ne permet de telles constructions , mais pour la plupart des cas cette réponse couvre encore.
Konrad Rudolph
1
@KonradRudolph Pas tout à fait vrai. D' abord c # ne vous permet de créer de nouveaux types lors de l' exécution. Vous ne pouvez tout simplement pas appeler quoi que ce soit sur eux d'une manière statiquement sûre . Alors oui, vous avez à moitié raison. Mais plus réaliste, vous en avez besoin lorsque vous chargez des assemblys au moment de l'exécution, ce qui signifie que le type n'est pas connu au moment de la compilation. C # serait sévèrement limité si vous ne pouviez pas le faire. Je veux dire que vous venez de le prouver vous-même: comment fonctionne la méthode Activator qui prend une instance de type? Lorsque MS a écrit la classe Activator, ils n'avaient aucune connaissance au moment de la compilation des futurs types que les utilisateurs écriraient.
AnorZaken
1
@AnorZaken Mon commentaire ne dit rien sur la création de types lors de l'exécution. Bien sûr, vous pouvez le faire, mais vous ne pouvez pas les utiliser statiquement dans le même contexte (vous pouvez bien sûr héberger un programme complet compilé statiquement). C'est tout ce que mon commentaire dit.
Konrad Rudolph
@KonradRudolph Ah désolé, mal interprété "une telle opération" pour signifier instancier un type qui n'est connu qu'au moment de l'exécution; au lieu de signifier utiliser un type d'exécution comme paramètre de type générique.
AnorZaken
1
@AnorZaken - techniquement, vous pouvez à la fois créer de nouveaux types au moment de l'exécution ET appeler des méthodes sur eux de manière statique si votre nouveau type implémente une interface connue ou hérite d'une classe de base connue. - L'une ou l'autre de ces approches vous donnera un contrat statique pour votre objet créé lors de l'exécution.
BrainSlugs83
132

L'expression compilée est le meilleur moyen! (pour que les performances créent plusieurs fois l'instance lors de l'exécution).

static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
 ).Compile();

X x = YCreator();

Statistiques (2012):

    Iterations: 5000000
    00:00:00.8481762, Activator.CreateInstance(string, string)
    00:00:00.8416930, Activator.CreateInstance(type)
    00:00:06.6236752, ConstructorInfo.Invoke
    00:00:00.1776255, Compiled expression
    00:00:00.0462197, new

Statistiques (2015, .net 4.5, x64):

    Iterations: 5000000
    00:00:00.2659981, Activator.CreateInstance(string, string)
    00:00:00.2603770, Activator.CreateInstance(type)
    00:00:00.7478936, ConstructorInfo.Invoke
    00:00:00.0700757, Compiled expression
    00:00:00.0286710, new

Statistiques (2015, .net 4.5, x86):

    Iterations: 5000000
    00:00:00.3541501, Activator.CreateInstance(string, string)
    00:00:00.3686861, Activator.CreateInstance(type)
    00:00:00.9492354, ConstructorInfo.Invoke
    00:00:00.0719072, Compiled expression
    00:00:00.0229387, new

Statistiques (2017, LINQPad 5.22.02 / x64 / .NET 4.6):

    Iterations: 5000000
    No args
    00:00:00.3897563, Activator.CreateInstance(string assemblyName, string typeName)
    00:00:00.3500748, Activator.CreateInstance(Type type)
    00:00:01.0100714, ConstructorInfo.Invoke
    00:00:00.1375767, Compiled expression
    00:00:00.1337920, Compiled expression (type)
    00:00:00.0593664, new
    Single arg
    00:00:03.9300630, Activator.CreateInstance(Type type)
    00:00:01.3881770, ConstructorInfo.Invoke
    00:00:00.1425534, Compiled expression
    00:00:00.0717409, new

Statistiques (2019, x64 / .NET 4.8):

Iterations: 5000000
No args
00:00:00.3287835, Activator.CreateInstance(string assemblyName, string typeName)
00:00:00.3122015, Activator.CreateInstance(Type type)
00:00:00.8035712, ConstructorInfo.Invoke
00:00:00.0692854, Compiled expression
00:00:00.0662223, Compiled expression (type)
00:00:00.0337862, new
Single arg
00:00:03.8081959, Activator.CreateInstance(Type type)
00:00:01.2507642, ConstructorInfo.Invoke
00:00:00.0671756, Compiled expression
00:00:00.0301489, new

Statistiques (2019, x64 / .NET Core 3.0):

Iterations: 5000000
No args
00:00:00.3226895, Activator.CreateInstance(string assemblyName, string typeName)
00:00:00.2786803, Activator.CreateInstance(Type type)
00:00:00.6183554, ConstructorInfo.Invoke
00:00:00.0483217, Compiled expression
00:00:00.0485119, Compiled expression (type)
00:00:00.0434534, new
Single arg
00:00:03.4389401, Activator.CreateInstance(Type type)
00:00:01.0803609, ConstructorInfo.Invoke
00:00:00.0554756, Compiled expression
00:00:00.0462232, new

Code complet:

static X CreateY_New()
{
    return new Y();
}

static X CreateY_New_Arg(int z)
{
    return new Y(z);
}

static X CreateY_CreateInstance()
{
    return (X)Activator.CreateInstance(typeof(Y));
}

static X CreateY_CreateInstance_String()
{
    return (X)Activator.CreateInstance("Program", "Y").Unwrap();
}

static X CreateY_CreateInstance_Arg(int z)
{
    return (X)Activator.CreateInstance(typeof(Y), new object[] { z, });
}

private static readonly System.Reflection.ConstructorInfo YConstructor =
    typeof(Y).GetConstructor(Type.EmptyTypes);
private static readonly object[] Empty = new object[] { };
static X CreateY_Invoke()
{
    return (X)YConstructor.Invoke(Empty);
}

private static readonly System.Reflection.ConstructorInfo YConstructor_Arg =
    typeof(Y).GetConstructor(new[] { typeof(int), });
static X CreateY_Invoke_Arg(int z)
{
    return (X)YConstructor_Arg.Invoke(new object[] { z, });
}

private static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
).Compile();
static X CreateY_CompiledExpression()
{
    return YCreator();
}

private static readonly Func<X> YCreator_Type = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y))
).Compile();
static X CreateY_CompiledExpression_Type()
{
    return YCreator_Type();
}

private static readonly ParameterExpression YCreator_Arg_Param = Expression.Parameter(typeof(int), "z");
private static readonly Func<int, X> YCreator_Arg = Expression.Lambda<Func<int, X>>(
   Expression.New(typeof(Y).GetConstructor(new[] { typeof(int), }), new[] { YCreator_Arg_Param, }),
   YCreator_Arg_Param
).Compile();
static X CreateY_CompiledExpression_Arg(int z)
{
    return YCreator_Arg(z);
}

static void Main(string[] args)
{
    const int iterations = 5000000;

    Console.WriteLine("Iterations: {0}", iterations);

    Console.WriteLine("No args");
    foreach (var creatorInfo in new[]
    {
        new {Name = "Activator.CreateInstance(string assemblyName, string typeName)", Creator = (Func<X>)CreateY_CreateInstance},
        new {Name = "Activator.CreateInstance(Type type)", Creator = (Func<X>)CreateY_CreateInstance},
        new {Name = "ConstructorInfo.Invoke", Creator = (Func<X>)CreateY_Invoke},
        new {Name = "Compiled expression", Creator = (Func<X>)CreateY_CompiledExpression},
        new {Name = "Compiled expression (type)", Creator = (Func<X>)CreateY_CompiledExpression_Type},
        new {Name = "new", Creator = (Func<X>)CreateY_New},
    })
    {
        var creator = creatorInfo.Creator;

        var sum = 0;
        for (var i = 0; i < 1000; i++)
            sum += creator().Z;

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        for (var i = 0; i < iterations; ++i)
        {
            var x = creator();
            sum += x.Z;
        }
        stopwatch.Stop();
        Console.WriteLine("{0}, {1}", stopwatch.Elapsed, creatorInfo.Name);
    }

    Console.WriteLine("Single arg");
    foreach (var creatorInfo in new[]
    {
        new {Name = "Activator.CreateInstance(Type type)", Creator = (Func<int, X>)CreateY_CreateInstance_Arg},
        new {Name = "ConstructorInfo.Invoke", Creator = (Func<int, X>)CreateY_Invoke_Arg},
        new {Name = "Compiled expression", Creator = (Func<int, X>)CreateY_CompiledExpression_Arg},
        new {Name = "new", Creator = (Func<int, X>)CreateY_New_Arg},
    })
    {
        var creator = creatorInfo.Creator;

        var sum = 0;
        for (var i = 0; i < 1000; i++)
            sum += creator(i).Z;

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        for (var i = 0; i < iterations; ++i)
        {
            var x = creator(i);
            sum += x.Z;
        }
        stopwatch.Stop();
        Console.WriteLine("{0}, {1}", stopwatch.Elapsed, creatorInfo.Name);
    }
}

public class X
{
  public X() { }
  public X(int z) { this.Z = z; }
  public int Z;
}

public class Y : X
{
    public Y() {}
    public Y(int z) : base(z) {}
}
Serj-Tm
la source
18
+1 pour toutes les statistiques! Je n'ai pas vraiment besoin de ce genre de performance pour le moment, mais toujours très intéressant. :)
AnorZaken
1
Il existe également TypeDescriptor.CreateInstance (voir stackoverflow.com/a/17797389/1242 ) qui peut être plus rapide s'il est utilisé avec TypeDescriptor.AddProvider
Lars Truijens
2
Est-ce toujours utile lorsque vous ne savez pas quel type Xest à l'exécution?
ajeh
1
@ajeh Oui. Remplacez typeof (T) par Type.GetType (..).
Serj-Tm
3
@ Serj-Tm Non, cela ne fonctionnera pas si le type X est un runtime Type.
NetMage
47

Une implémentation de ce problème consiste à essayer d'appeler le constructeur sans paramètre du type:

public static object GetNewObject(Type t)
{
    try
    {
        return t.GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return null;
    }
}

Voici la même approche, contenue dans une méthode générique:

public static T GetNewObject<T>()
{
    try
    {
        return (T)typeof(T).GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return default(T);
    }
}
tags2k
la source
15
Programmation axée sur les exceptions? Cela semble être une implémentation très médiocre lorsque vous pouvez simplement réfléchir au type pour déterminer les constructeurs.
Firoso
16

C'est assez simple. Supposons que votre nom de classe est Caret que l'espace de noms est Vehicles, puis passez le paramètre comme Vehicles.Carlequel renvoie l'objet de type Car. Comme cela, vous pouvez créer dynamiquement n'importe quelle instance de n'importe quelle classe.

public object GetInstance(string strNamesapace)
{         
     Type t = Type.GetType(strNamesapace); 
     return  Activator.CreateInstance(t);         
}

Si votre nom complet (c'est- Vehicles.Carà- dire dans ce cas) se trouve dans un autre assembly, le Type.GetTypesera nul. Dans de tels cas, vous devez parcourir tous les assemblys et trouver le fichier Type. Pour cela, vous pouvez utiliser le code ci-dessous

public object GetInstance(string strFullyQualifiedName)
{
     Type type = Type.GetType(strFullyQualifiedName);
     if (type != null)
         return Activator.CreateInstance(type);
     foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
     {
         type = asm.GetType(strFullyQualifiedName);
         if (type != null)
             return Activator.CreateInstance(type);
     }
     return null;
 }

Et vous pouvez obtenir l'instance en appelant la méthode ci-dessus.

object objClassInstance = GetInstance("Vehicles.Car");
Sarath Avanavu
la source
Dans votre deuxième cas (assemblage externe), vous pouvez simplement passer "Vehicles.Car, OtherAssembly" à votre première méthode et cela fonctionnera. Évidemment, OtherAssembly est le nom de l'assemblage dans
lequel
2
@danmiser Cela nécessite un codage en dur du nom de l'assembly. Afin de mettre en œuvre la flexibilité, je vérifie null et le code fonctionne de manière dynamique :)
Sarath Avanavu
14

Si c'est pour quelque chose qui sera appelé beaucoup dans une instance d'application, il est beaucoup plus rapide de compiler et de mettre en cache du code dynamique au lieu d'utiliser l'activateur ou ConstructorInfo.Invoke(). Les deux options simples pour la compilation dynamique sont les expressions Linq compilées ou certains ILopcodesDynamicMethod simples et . Quoi qu'il en soit, la différence est énorme lorsque vous commencez à effectuer des boucles serrées ou plusieurs appels.

Tom Mayfield
la source
11

Le générique ne T t = new T();fonctionnerait -il pas ?

Brady Moritz
la source
9
En fait, ce serait dans une classe / méthode générique, mais pas pour un "Type" donné.
Brady Moritz
Suppose que le type T a la contrainte 'new ()'.
Rob Von Nesselrode
10

Si vous souhaitez utiliser le constructeur par défaut, la solution utilisant System.Activatorprésentée précédemment est probablement la plus pratique. Cependant, si le type n'a pas de constructeur par défaut ou si vous devez en utiliser un autre, alors une option est d'utiliser la réflexion ou System.ComponentModel.TypeDescriptor. En cas de réflexion, il suffit de connaître uniquement le nom du type (avec son espace de noms).

Exemple utilisant la réflexion:

ObjectType instance = 
    (ObjectType)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(
        typeName: objectType.FulName, // string including namespace of the type
        ignoreCase: false,
        bindingAttr: BindingFlags.Default,
        binder: null,  // use default binder
        args: new object[] { args, to, constructor },
        culture: null, // use CultureInfo from current thread
        activationAttributes: null
    );

Exemple utilisant TypeDescriptor:

ObjectType instance = 
    (ObjectType)System.ComponentModel.TypeDescriptor.CreateInstance(
        provider: null, // use standard type description provider, which uses reflection
        objectType: objectType,
        argTypes: new Type[] { types, of, args },
        args: new object[] { args, to, constructor }
    );
BSharp
la source
args[]était exactement ce que je suis venu à cette question pour trouver, merci!
Chad
10

Sans utilisation de la réflexion:

private T Create<T>() where T : class, new()
{
    return new T();
}
meagar
la source
5
Comment est-ce utile? Vous devez déjà connaître le type pour appeler cette méthode, et si vous connaissez le type, vous pouvez le construire sans méthode spéciale.
Kyle Delaney
T peut donc varier lors de l'exécution. Utile si vous travaillez avec des types dérivés.
un nouveau T (); échouerait si T n'est pas un type de référence avec un constructeur sans paramètre. Cette méthode utilise des contraintes pour garantir que T est un type de référence et possède un constructeur.
3
Comment T peut-il varier à l'exécution? Ne devez-vous pas connaître T au moment de la conception pour appeler Créer <>?
Kyle Delaney du
Si vous travaillez avec des classes et des interfaces génériques dans des usines, les types qui implémentent l'interface doivent être instanciés peuvent varier.
8

Compte tenu de ce problème, l'activateur fonctionnera lorsqu'il y a un ctor sans paramètre. S'il s'agit d'une contrainte, envisagez d'utiliser

System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()
Thulani Chivandikwa
la source
5
public AbstractType New
{
    get
    {
        return (AbstractType) Activator.CreateInstance(GetType());
    }
}
vikram nayak
la source
4

Je peux traverser cette question parce que je cherchais à implémenter une méthode CloneObject simple pour une classe arbitraire (avec un constructeur par défaut)

Avec la méthode générique, vous pouvez exiger que le type implémente New ().

Public Function CloneObject(Of T As New)(ByVal src As T) As T
    Dim result As T = Nothing
    Dim cloneable = TryCast(src, ICloneable)
    If cloneable IsNot Nothing Then
        result = cloneable.Clone()
    Else
        result = New T
        CopySimpleProperties(src, result, Nothing, "clone")
    End If
    Return result
End Function

Avec non générique, supposez que le type a un constructeur par défaut et interceptez une exception si ce n'est pas le cas.

Public Function CloneObject(ByVal src As Object) As Object
    Dim result As Object = Nothing
    Dim cloneable As ICloneable
    Try
        cloneable = TryCast(src, ICloneable)
        If cloneable IsNot Nothing Then
            result = cloneable.Clone()
        Else
            result = Activator.CreateInstance(src.GetType())
            CopySimpleProperties(src, result, Nothing, "clone")
        End If
    Catch ex As Exception
        Trace.WriteLine("!!! CloneObject(): " & ex.Message)
    End Try
    Return result
End Function
Darrel Lee
la source