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?

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

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

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) {}
}

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

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

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.


11

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


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

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

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

5
public AbstractType New
{
    get
    {
        return (AbstractType) Activator.CreateInstance(GetType());
    }
}

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.