Creazione di istanze di tipo senza costruttore predefinito in C # utilizzando la reflection


97

Prendi la seguente classe come esempio:

class Sometype
{
    int someValue;

    public Sometype(int someValue)
    {
        this.someValue = someValue;
    }
}

Quindi voglio creare un'istanza di questo tipo usando la riflessione:

Type t = typeof(Sometype);
object o = Activator.CreateInstance(t);

Normalmente questo funzionerà, tuttavia poiché SomeTypenon è stato definito un costruttore senza parametri, la chiamata a Activator.CreateInstancegenererà un'eccezione di tipo MissingMethodExceptioncon il messaggio " Nessun costruttore senza parametri definito per questo oggetto " . Esiste un modo alternativo per creare ancora un'istanza di questo tipo? Sarebbe un po 'schifoso aggiungere costruttori senza parametri a tutte le mie classi.


2
FormatterServices.GetUninitializedObjectnon consente di creare stringhe non inizializzate. Potresti ottenere un'eccezione: System.ArgumentException: Uninitialized Strings cannot be created.tienilo a mente.
Bartosz Pierzchlewicz

Grazie per l'avviso, ma sto già gestendo le corde e i tipi di base separatamente.
Aistina

Risposte:


142

Inizialmente ho pubblicato questa risposta qui , ma ecco una ristampa poiché questa non è la stessa identica domanda ma ha la stessa risposta:

FormatterServices.GetUninitializedObject()creerà un'istanza senza chiamare un costruttore. Ho trovato questa classe usando Reflector e scavando attraverso alcune delle principali classi di serializzazione .Net.

L'ho testato utilizzando il codice di esempio di seguito e sembra che funzioni alla grande:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.Serialization;

namespace NoConstructorThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass)); //does not call ctor
            myClass.One = 1;
            Console.WriteLine(myClass.One); //write "1"
            Console.ReadKey();
        }
    }

    public class MyClass
    {
        public MyClass()
        {
            Console.WriteLine("MyClass ctor called.");
        }

        public int One
        {
            get;
            set;
        }
    }
}

Fantastico, sembra proprio quello di cui ho bisogno. Presumo che non inizializzato significhi che tutta la sua memoria sarà impostata su zero? (Simile a come vengono istanziate le strutture)
Aistina

Qualunque sia il valore predefinito per ogni tipo sarà il valore predefinito. Quindi gli oggetti saranno null, int 0, ecc. Penso che avvenga qualsiasi inizializzazione a livello di classe, ma non viene eseguito alcun costruttore.
Jason Jackson

14
@JSBangs, che fa schifo stai dando una risposta perfettamente legittima. Il tuo commento e l'altra risposta non rispondono effettivamente alla domanda posta. Se ritieni di avere una risposta migliore, forniscine una. Ma la risposta che ho fornito evidenzia come utilizzare una classe documentata nello stesso modo in cui altre classi di serializzazione usano questo codice.
Jason Jackson,

21
@JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) non è documentato.
Autodidact


72

Usa questo overload del metodo CreateInstance:

public static Object CreateInstance(
    Type type,
    params Object[] args
)

Crea un'istanza del tipo specificato utilizzando il costruttore che meglio corrisponde ai parametri specificati.

Vedi: http://msdn.microsoft.com/en-us/library/wcxyzt4d.aspx


1
Questa soluzione semplifica eccessivamente il problema. E se non conosco il mio tipo e dico "crea semplicemente un oggetto della variabile Type in this Type"?
kamii

23

Quando ho confrontato le prestazioni di (T)FormatterServices.GetUninitializedObject(typeof(T))esso era più lento. Allo stesso tempo, le espressioni compilate offrono grandi miglioramenti della velocità sebbene funzionino solo per i tipi con il costruttore predefinito. Ho adottato un approccio ibrido:

public static class New<T>
{
    public static readonly Func<T> Instance = Creator();

    static Func<T> Creator()
    {
        Type t = typeof(T);
        if (t == typeof(string))
            return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile();

        if (t.HasDefaultConstructor())
            return Expression.Lambda<Func<T>>(Expression.New(t)).Compile();

        return () => (T)FormatterServices.GetUninitializedObject(t);
    }
}

public static bool HasDefaultConstructor(this Type t)
{
    return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
}

Ciò significa che l'espressione create viene effettivamente memorizzata nella cache e incorre in penalità solo la prima volta che il tipo viene caricato. Gestirà anche i tipi di valore in modo efficiente.

Chiamalo:

MyType me = New<MyType>.Instance();

Nota che (T)FormatterServices.GetUninitializedObject(t)fallirà per la stringa. Quindi una gestione speciale per la stringa è in atto per restituire una stringa vuota.


1
È strano come uno sguardo a una riga del codice di qualcuno possa salvare un giorno. Grazie Signore! I motivi delle prestazioni mi hanno portato al tuo post e il trucco è fatto :) Le classi FormatterServices e Activator sono sottoperformanti rispetto alle espressioni compilate, che peccato si trovino gli Activator ovunque.
jmodrak

@nawfal Per quanto riguarda la tua gestione speciale per le stringhe, so che non funzionerebbe per le stringhe senza questa gestione speciale, ma voglio solo sapere: funzionerà per tutti gli altri tipi?
Sнаđошƒаӽ

@ Sнаđошƒаӽ purtroppo no. L'esempio fornito è barebone e .NET ha molti tipi diversi di tipi. Ad esempio, considera, se passi un tipo di delegato, come gli darai un'istanza? Oppure se il costruttore lancia cosa puoi fare al riguardo? Molti modi diversi per gestirlo. Da allora ho risposto a questo aggiornamento per gestire molti più scenari nella mia libreria. Per ora non è pubblicato da nessuna parte.
nawfal

4

Buone risposte ma inutilizzabili sul framework dot net compact. Ecco una soluzione che funzionerà su CF.Net ...

class Test
{
    int _myInt;

    public Test(int myInt)
    {
        _myInt = myInt;
    }

    public override string ToString()
    {
        return "My int = " + _myInt.ToString();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var ctor = typeof(Test).GetConstructor(new Type[] { typeof(int) });
        var obj = ctor.Invoke(new object[] { 10 });
        Console.WriteLine(obj);
    }
}

1
Questo è il modo in cui chiamerei un costruttore non predefinito. Non sono sicuro che avrei mai voluto creare un oggetto senza chiamare alcun costruttore.
Rory MacLeod

2
Potresti voler creare un oggetto senza chiamare i costruttori se stai scrivendo serializzatori personalizzati.
Autodidatta

1
Sì, questo è lo scenario di caso d'uso esatto per cui questa domanda era :)
Aistina

1
@Aistina Forse potresti aggiungere queste informazioni alla domanda? La maggior parte delle persone sarebbe contraria alla creazione di oggetti senza chiamare i propri medici e si prenderebbe il tempo per discuterne con te, ma il tuo caso d'uso in realtà lo giustifica, quindi penso che sia molto rilevante per la domanda stessa.
julealgon
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.