Comment créer une nouvelle instance d'objet à partir d'un type
-
08-06-2019 - |
Question
On ne connaît pas toujours le Type
d'un objet au moment de la compilation, mais il peut être nécessaire de créer une instance du Type
.Comment obtenir une nouvelle instance d'objet à partir d'un Type
?
La solution
Le Activator
classe dans la racine System
l'espace de noms est assez puissant.
Il existe de nombreuses surcharges pour transmettre des paramètres au constructeur, etc.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");
Autres conseils
ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);
Le Activator
class a une variante générique qui rend cela un peu plus facile :
ObjectType instance = Activator.CreateInstance<ObjectType>();
L'expression compilée est la meilleure solution !(pour que les performances créent une instance à plusieurs reprises 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) {}
}
Une implémentation de ce problème consiste à tenter 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);
}
}
C'est assez simple.Supposons que votre nom de classe soit Car
et l'espace de noms est Vehicles
, puis transmettez le paramètre comme Vehicles.Car
qui renvoie un objet de type Car
.De cette façon, 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 ton Nom complet(c'est à dire, Vehicles.Car
dans ce cas) est dans une autre assemblée, le Type.GetType
sera nul.Dans de tels cas, vous parcourez tous les assemblages et trouvez le 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");
S'il s'agit de quelque chose qui sera souvent appelé 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()
.Deux options simples pour la compilation dynamique sont compilées Expressions Linq ou un simple IL
les opcodes et DynamicMethod
.Quoi qu’il en soit, la différence est énorme lorsque vous commencez à vous retrouver dans des boucles serrées ou dans plusieurs appels.
Sans utilisation de Reflection :
private T Create<T>() where T : class, new()
{
return new T();
}
Le générique ne serait-il pas T t = new T();
travail?
Si vous souhaitez utiliser le constructeur par défaut, la solution utilisant System.Activator
présenté plus tôt est probablement le plus pratique.Cependant, si le type ne dispose pas d'un constructeur par défaut ou si vous devez en utiliser un autre que celui par défaut, une option consiste à utiliser la réflexion ou System.ComponentModel.TypeDescriptor
.En cas de réflexion, il suffit de connaître juste 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 }
);
Compte tenu de ce problème, l'Activator fonctionnera lorsqu'il y aura un cteur sans paramètre.S'il s'agit d'une contrainte, envisagez d'utiliser
System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()
Je peux répondre à 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, supposons que le type a un constructeur par défaut et captez 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
public AbstractType New
{
get
{
return (AbstractType) Activator.CreateInstance(GetType());
}
}