Come creare una nuova istanza di oggetto da un Tipo


747

Uno potrebbe non sempre conoscere il Typedi un oggetto in fase di compilazione, ma potrebbe essere necessario creare un'istanza di Type.

Come si ottiene una nuova istanza di oggetto da un Type?

Risposte:


896

La Activatorclasse nello Systemspazio dei nomi di root è piuttosto potente.

Ci sono molti sovraccarichi per il passaggio dei parametri al costruttore e simili. Consulta la documentazione su:

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

o (nuovo percorso)

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

Ecco alcuni semplici esempi:

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

ObjectType instance = (ObjectType)Activator.CreateInstance("MyAssembly","MyNamespace.ObjectType");

21
Sono contento di averlo finalmente trovato, ma la seconda chiamata non è esattamente corretta, mancando una citazione e parms invertiti, dovrebbe essere: ObjectType instance = (ObjectType) Activator.CreateInstance ("MyAssembly", "MyNamespace.ObjectType");
Kevin,

10
Devi chiamare 'Unwrap ()' per ottenere il tipo di oggetto che desideri: ConcreteType instance = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Ε Г И І И О

4
In che modo ObjectType instancecorrisponde la condizione del PO "Uno potrebbe non conoscere sempre il tipo di un oggetto in fase di compilazione"? : P
Martin Schneider,

@ MA-Maddin va bene allora object instance = Activator.CreateInstance(...);.
BrainSlugs83,

1
Qualcuno sa come farlo in .NET Core? Il metodo Unwrap non è disponibile sull'oggetto.
Justin il

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

La Activatorclasse ha una variante generica che lo rende un po 'più semplice:

ObjectType instance = Activator.CreateInstance<ObjectType>();

8
@Kevin Certo. Tale operazione non può funzionare in un linguaggio tipicamente statico perché non ha senso. Non è possibile richiamare metodi su un oggetto di tipo sconosciuto. Nel frattempo (= dal scrivendo questa risposta) C # ha ottenuto il dynamiccostrutto che fa permettere tali costrutti, ma per la maggior parte scopi questa risposta copre ancora.
Konrad Rudolph,

1
@KonradRudolph Non del tutto vero. Prima di c # non consentono di creare nuovi tipi in fase di esecuzione. Non puoi chiamare nulla su di loro in modo staticamente sicuro . Quindi sì, hai quasi ragione. Ma più realisticamente è necessario quando si caricano gli assembly in fase di esecuzione, il che significa che il tipo non è noto al momento della compilazione. C # sarebbe fortemente limitato se non ci riuscissi. Voglio dire, lo hai appena dimostrato tu stesso: in che altro modo funziona il metodo Activator che accetta un'istanza di tipo? Quando MS ha scritto la classe Activator non aveva alcuna conoscenza in fase di compilazione di eventuali tipi futuri che gli utenti avrebbero scritto.
AnorZaken,

1
@AnorZaken Il mio commento non dice nulla sulla creazione di tipi in fase di esecuzione. Ovviamente puoi farlo, ma non puoi usarli staticamente nello stesso contesto (puoi ovviamente ospitare un programma completo compilato staticamente). Questo è tutto ciò che il mio commento sta dicendo.
Konrad Rudolph,

@KonradRudolph Ah scusa, ho interpretato erroneamente "tale operazione" per indicare un'istanza di un tipo che è noto solo in fase di esecuzione; invece di utilizzare un tipo di runtime come parametro di tipo generico.
AnorZaken,

1
@AnorZaken: tecnicamente puoi sia creare nuovi tipi in fase di esecuzione sia chiamare metodi su di essi in modo staticamente sicuro se il tuo nuovo tipo implementa un'interfaccia nota o eredita una classe base nota. - Entrambi questi approcci ti daranno un contratto statico per l'oggetto creato in fase di esecuzione.
BrainSlugs83,

132

L'espressione compilata è il modo migliore! (affinché le prestazioni creino ripetutamente istanza in runtime).

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

X x = YCreator();

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

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

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

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

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

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

Codice completo:

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 per tutte le statistiche! Non ho davvero bisogno di questo tipo di esibizione al momento, ma comunque molto interessante. :)
AnorZaken,

1
Inoltre v'è TypeDescriptor.CreateInstance (vedi stackoverflow.com/a/17797389/1242 ), che può essere più veloce se utilizzato con TypeDescriptor.AddProvider
Lars Truijens

2
È ancora utile quando non sai che tipo Xè in fase di esecuzione?
ajeh

1
@ajeh Sì. Cambia typeof (T) in Type.GetType (..).
Serj-Tm,

3
@ Serj-Tm No, non funzionerà se il tipo X è un runtime Type.
NetMage,

47

Un'implementazione di questo problema è tentare di chiamare il costruttore senza parametri del tipo:

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

Ecco lo stesso approccio, contenuto in un metodo generico:

public static T GetNewObject<T>()
{
    try
    {
        return (T)typeof(T).GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return default(T);
    }
}

15
Programmazione guidata da eccezioni? Sembra un'implementazione molto scadente quando puoi semplicemente riflettere sul tipo per determinare i costruttori.
Firoso,

16

È piuttosto semplice. Supponiamo che il tuo nome classe sia Care lo spazio dei nomi sia Vehicles, quindi passa il parametro come Vehicles.Carche restituisce l'oggetto di tipo Car. In questo modo puoi creare dinamicamente qualsiasi istanza di qualsiasi classe.

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

Se il tuo nome completo (ovvero, Vehicles.Carin questo caso) si trova in un altro assembly, Type.GetTypesarà nullo. In tali casi, è necessario scorrere tutti gli assiemi e trovare il file Type. Per questo puoi usare il codice qui sotto

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

E puoi ottenere l'istanza chiamando il metodo sopra.

object objClassInstance = GetInstance("Vehicles.Car");

Nel tuo secondo caso (assemblaggio esterno), potresti semplicemente passare "Vehicles.Car, OtherAssembly" al tuo primo metodo e funzionerà. Ovviamente AltroAssembly è il nome dell'assemblea in cui vive.
danmiser,

2
@danmiser che ha bisogno di codificare il nome dell'assembly. Per implementare la flessibilità, sto controllando null e il codice funziona in modo dinamico :)
Sarath Avanavu,

14

Se questo è per qualcosa che verrà chiamato molto in un'istanza dell'applicazione, è molto più veloce compilare e memorizzare nella cache il codice dinamico invece di usare l'attivatore o ConstructorInfo.Invoke(). Due semplici opzioni per la compilazione dinamica sono le espressioni Linq compilate o alcuni semplici ILcodici operativi eDynamicMethod . In entrambi i casi, la differenza è enorme quando si inizia a fare loop ravvicinati o più chiamate.



10

Se si desidera utilizzare il costruttore predefinito, la soluzione che viene System.Activatorpresentata precedentemente è probabilmente la più conveniente. Tuttavia, se il tipo è privo di un costruttore predefinito o se si deve utilizzare uno non predefinito, un'opzione è di usare reflection o System.ComponentModel.TypeDescriptor. In caso di riflessione, è sufficiente conoscere solo il nome del tipo (con il suo spazio dei nomi).

Esempio usando reflection:

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

Esempio usando 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[]era esattamente quello che sono venuto a questa domanda per trovare, grazie!
Chad,

10

Senza l'uso di Reflection:

private T Create<T>() where T : class, new()
{
    return new T();
}

5
Quanto è utile? Devi conoscere il tipo già per chiamare quel metodo e se conosci il tipo puoi costruirlo senza un metodo speciale.
Kyle Delaney,

Quindi T può variare in fase di esecuzione. Utile se lavori con tipi derivati.

un nuovo T (); fallirebbe se T non fosse un tipo di riferimento con un costruttore senza parametri, questo metodo usa dei contorni per assicurarsi che T sia un tipo di riferimento e abbia un costruttore.

3
Come può variare T in fase di esecuzione? Non è necessario conoscere T in fase di progettazione per chiamare Crea <>?
Kyle Delaney,

Se lavori con classi e interfacce generiche nelle fabbriche, i tipi che implementano l'interfaccia dovrebbero essere istanziati possono variare.

8

Dato questo problema, l'attivatore funzionerà in presenza di un ctor senza parametri. Se questo è un vincolo, considerare l'utilizzo

System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()

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

4

Posso rispondere a questa domanda perché stavo cercando di implementare un metodo CloneObject semplice per una classe arbitraria (con un costruttore predefinito)

Con il metodo generico puoi richiedere che il tipo implementi 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

Con un non generico supponiamo che il tipo abbia un costruttore predefinito e se non lo rileva cattura un'eccezione.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.