So erstellen Sie eine neue Objektinstanz aus einem Typ

748

Möglicherweise kennt man das TypeObjekt zur Kompilierungszeit nicht immer , muss jedoch möglicherweise eine Instanz des Objekts erstellen Type.

Wie erhält man eine neue Objektinstanz von a Type?

tags2k
quelle

Antworten:

896

Die ActivatorKlasse im Root- SystemNamespace ist ziemlich mächtig.

Es gibt viele Überladungen für die Übergabe von Parametern an den Konstruktor und dergleichen. Lesen Sie die Dokumentation unter:

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

oder (neuer Pfad)

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

Hier einige einfache Beispiele:

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

ObjectType instance = (ObjectType)Activator.CreateInstance("MyAssembly","MyNamespace.ObjectType");
Karl Seguin
quelle
21
Ich bin froh, dies endlich gefunden zu haben, aber der zweite Aufruf ist nicht genau richtig, da ein Anführungszeichen fehlt und die Parameter umgekehrt sind, sollte lauten: ObjectType instance = (ObjectType) Activator.CreateInstance ("MyAssembly", "MyNamespace.ObjectType");
Kevin
10
Sie müssen 'Unwrap ()' aufrufen, um den gewünschten Objekttyp zu erhalten: ConcreteType instance = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Г И І І О
4
Wie stimmt ObjectType instancedie Bedingung des OP überein, dass der Typ eines Objekts zur Kompilierungszeit möglicherweise nicht immer bekannt ist? : P
Martin Schneider
@ MA-Maddin okay dann , object instance = Activator.CreateInstance(...);.
BrainSlugs83
1
Weiß jemand, wie man das in .NET Core macht? Die Unwrap-Methode ist für Objekte nicht verfügbar.
Justin
145
ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);

Die ActivatorKlasse hat eine generische Variante, die dies etwas einfacher macht:

ObjectType instance = Activator.CreateInstance<ObjectType>();
Konrad Rudolph
quelle
8
@ Kevin Natürlich. Eine solche Operation kann in einer statisch typisierten Sprache nicht funktionieren, da sie keinen Sinn ergibt. Sie können keine Methoden für ein Objekt unbekannten Typs aufrufen. In der Zwischenzeit (= diese Antwort , da das Schreiben) C # hat das bekam dynamicKonstrukt , das tut solchen Konstrukte erlaubt , aber für die meisten Zwecke dieser Antwort noch bedeckt.
Konrad Rudolph
1
@KonradRudolph Nicht ganz richtig. Zunächst c # nicht erlauben Ihnen , neue Typen zur Laufzeit zu erstellen. Sie können sie einfach nicht auf statisch sichere Weise anrufen . Also ja, du bist halb richtig. Realistischer ist dies jedoch, wenn Sie Assemblys zur Laufzeit laden. Dies bedeutet, dass der Typ zur Kompilierungszeit nicht bekannt ist. C # wäre stark eingeschränkt, wenn Sie dies nicht tun könnten. Ich meine, Sie haben es gerade selbst bewiesen: Wie funktioniert sonst die Activator-Methode, die eine Typinstanz benötigt? Als MS die Activator-Klasse schrieb, hatten sie keine Kenntnisse zur Kompilierungszeit über zukünftige Typen, die Benutzer schreiben würden.
AnorZaken
1
@AnorZaken Mein Kommentar sagt nichts über das Erstellen von Typen zur Laufzeit aus. Natürlich können Sie das tun, aber Sie können sie nicht statisch im selben Kontext verwenden (Sie können natürlich ein vollständiges statisch kompiliertes Programm hosten). Das ist alles, was mein Kommentar sagt.
Konrad Rudolph
@KonradRudolph Ah, tut mir leid, ich habe "eine solche Operation" falsch interpretiert, um einen Typ zu instanziieren, der nur zur Laufzeit bekannt ist. anstatt einen Laufzeit-Typ als generischen Typparameter zu verwenden.
AnorZaken
1
@AnorZaken - Technisch gesehen können Sie zur Laufzeit neue Typen erstellen UND Methoden auf statisch sichere Weise aufrufen, wenn Ihr neuer Typ eine bekannte Schnittstelle implementiert oder eine bekannte Basisklasse erbt. - Bei beiden Ansätzen erhalten Sie einen statischen Vertrag für Ihr zur Laufzeit erstelltes Objekt.
BrainSlugs83
132

Kompilierter Ausdruck ist der beste Weg! (Damit die Leistung zur Laufzeit wiederholt eine Instanz erstellt).

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

X x = YCreator();

Statistik (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

Statistik (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

Statistik (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

Statistiken (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

Statistik (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

Statistiken (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

Vollständiger Code:

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
quelle
18
+1 für alle Statistiken! Ich brauche diese Art von Performance im Moment nicht wirklich, aber immer noch sehr interessant. :)
AnorZaken
1
Es gibt auch TypeDescriptor.CreateInstance (siehe stackoverflow.com/a/17797389/1242 ), das bei Verwendung mit TypeDescriptor.AddProvider
Lars Truijens am
2
Ist dies immer noch nützlich, wenn Sie nicht wissen, welcher Typ Xzur Laufzeit ist?
Ajeh
1
@ajeh Ja. Ändern Sie typeof (T) in Type.GetType (..).
Serj-Tm
3
@ Serj-Tm Nein, das funktioniert nicht, wenn Typ X eine Laufzeit ist Type.
NetMage
47

Eine Implementierung dieses Problems besteht darin, zu versuchen, den parameterlosen Konstruktor des Typs aufzurufen:

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

Hier ist der gleiche Ansatz, der in einer generischen Methode enthalten ist:

public static T GetNewObject<T>()
{
    try
    {
        return (T)typeof(T).GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return default(T);
    }
}
tags2k
quelle
15
Ausnahmegesteuerte Programmierung? Dies scheint eine sehr schlechte Implementierung zu sein, wenn Sie einfach über den Typ nachdenken können, um Konstruktoren zu bestimmen.
Firoso
16

Es ist ziemlich einfach. Angenommen, Ihr Klassenname ist Carund der Namespace ist Vehicles, und übergeben Sie den Parameter, Vehicles.Carder ein Objekt vom Typ zurückgibt Car. Auf diese Weise können Sie jede Instanz einer beliebigen Klasse dynamisch erstellen.

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

Wenn sich Ihr vollständig qualifizierter Name (dh Vehicles.Carin diesem Fall) in einer anderen Assembly befindet, ist der NameType.GetType null. In solchen Fällen haben Sie eine Schleife durch alle Baugruppen und finden die Type. Dafür können Sie den folgenden Code verwenden

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

Und Sie können die Instanz erhalten, indem Sie die obige Methode aufrufen.

object objClassInstance = GetInstance("Vehicles.Car");
Sarath Avanavu
quelle
In Ihrem zweiten Fall (externe Baugruppe) können Sie einfach "Vehicles.Car, OtherAssembly" an Ihre erste Methode übergeben, und es wird funktionieren. Offensichtlich ist OtherAssembly der Name der Assembly, in der sie lebt.
Danmiser
2
@danmiser Das erfordert eine harte Codierung des Assemblynamens. Um Flexibilität zu implementieren, überprüfe ich null und der Code funktioniert dynamisch :)
Sarath Avanavu
14

Wenn dies für etwas ist, das in einer Anwendungsinstanz häufig aufgerufen wird, ist es viel schneller, dynamischen Code zu kompilieren und zwischenzuspeichern, anstatt den Aktivator oder zu verwenden ConstructorInfo.Invoke(). Es werden zwei einfache Optionen für die dynamische Kompilierung kompiliert Linq Expressions oder einige einfache ILOpcodes undDynamicMethod . In beiden Fällen ist der Unterschied sehr groß, wenn Sie in enge Schleifen oder mehrere Anrufe geraten.

Tom Mayfield
quelle
11

Würde das Generikum nicht T t = new T();funktionieren?

Brady Moritz
quelle
9
Eigentlich wäre es in einer generischen Klasse / Methode, aber nicht für einen bestimmten "Typ".
Brady Moritz
Angenommen, der Typ T hat die Einschränkung 'new ()'.
Rob Von Nesselrode
10

Wenn Sie den Standardkonstruktor verwenden möchten, ist die System.Activatorzuvor vorgestellte Lösung wahrscheinlich die bequemste. Wenn dem Typ jedoch ein Standardkonstruktor fehlt oder Sie einen nicht standardmäßigen Konstruktor verwenden müssen, können Sie Reflection oder verwenden System.ComponentModel.TypeDescriptor. Im Falle einer Reflexion reicht es aus, nur den Typnamen (mit seinem Namespace) zu kennen.

Beispiel mit Reflexion:

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

Beispiel mit 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
quelle
args[]war genau das, was ich zu dieser Frage gefunden habe, danke!
Tschad
10

Ohne Verwendung von Reflexion:

private T Create<T>() where T : class, new()
{
    return new T();
}
meagar
quelle
5
Wie ist das nützlich? Sie müssen den Typ bereits kennen, um diese Methode aufzurufen, und wenn Sie den Typ kennen, können Sie ihn ohne eine spezielle Methode erstellen.
Kyle Delaney
T kann also zur Laufzeit variieren. Nützlich, wenn Sie mit abgeleiteten Typen arbeiten.
ein neues T (); würde fehlschlagen, wenn T kein Referenztyp mit parameterlosem Konstruktor ist. Diese Methode verwendet Einschränkungen, um sicherzustellen, dass T ein Referenztyp ist und einen Konstruktor hat.
3
Wie kann T zur Laufzeit variieren? Müssen Sie T zur Entwurfszeit nicht kennen, um Create <> aufrufen zu können?
Kyle Delaney
Wenn Sie mit generischen Klassen und Schnittstellen in Fabriken arbeiten, können die Typen, die die Schnittstelle implementieren, instanziiert werden, variieren.
8

Aufgrund dieses Problems funktioniert der Aktivator, wenn ein parameterloser Ctor vorhanden ist. Wenn dies eine Einschränkung ist, ziehen Sie die Verwendung in Betracht

System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()
Thulani Chivandikwa
quelle
5
public AbstractType New
{
    get
    {
        return (AbstractType) Activator.CreateInstance(GetType());
    }
}
Vikram Nayak
quelle
4

Ich kann diese Frage beantworten, weil ich eine einfache CloneObject-Methode für eine beliebige Klasse (mit einem Standardkonstruktor) implementieren wollte.

Mit der generischen Methode können Sie verlangen, dass der Typ New () implementiert.

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

Bei nicht generischen Annahmen wird angenommen, dass der Typ einen Standardkonstruktor hat, und es wird eine Ausnahme abgefangen, wenn dies nicht der Fall ist.

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
quelle