Erstellen einer Instanz eines Typs ohne Standardkonstruktor in C # mithilfe von Reflection

97

Nehmen Sie die folgende Klasse als Beispiel:

class Sometype
{
    int someValue;

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

Ich möchte dann eine Instanz dieses Typs mithilfe von Reflection erstellen:

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

Normalerweise funktioniert dies. Da SomeTypejedoch kein parameterloser Konstruktor definiert wurde, löst der Aufruf von Activator.CreateInstanceeine Ausnahme vom Typ MissingMethodExceptionmit der Meldung " Kein parameterloser Konstruktor für dieses Objekt definiert " aus. Gibt es eine alternative Möglichkeit, eine Instanz dieses Typs weiterhin zu erstellen? Es wäre ein bisschen blöd, allen meinen Klassen parameterlose Konstruktoren hinzuzufügen.

Aistina
quelle
2
FormatterServices.GetUninitializedObjectNicht initialisierte Zeichenfolge darf nicht erstellt werden. Möglicherweise erhalten Sie eine Ausnahme: System.ArgumentException: Uninitialized Strings cannot be created.Bitte beachten Sie dies.
Bartosz Pierzchlewicz
Vielen Dank für das Heads-up, aber ich behandle Strings und Basistypen bereits separat.
Aistina

Antworten:

142

Ich stellte ursprünglich diese Antwort hier , aber hier ist ein Nachdruck , da dies nicht genau die gleiche Frage ist , hat aber die gleiche Antwort:

FormatterServices.GetUninitializedObject()erstellt eine Instanz, ohne einen Konstruktor aufzurufen. Ich habe diese Klasse mit Reflector gefunden und einige der wichtigsten .Net-Serialisierungsklassen durchsucht.

Ich habe es mit dem folgenden Beispielcode getestet und es sieht so aus, als ob es großartig funktioniert:

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;
        }
    }
}
Jason Jackson
quelle
Super, sieht so aus, als ob ich genau das brauche. Ich gehe davon aus, dass nicht initialisiert bedeutet, dass der gesamte Speicher auf Null gesetzt wird. (Ähnlich wie Strukturen instanziiert werden)
Aistina
Was auch immer der Standardwert für jeden Typ ist, ist der Standardwert. Objekte sind also null, ints 0 usw. Ich denke, dass eine Initialisierung auf Klassenebene stattfindet, aber kein Konstruktor ausgeführt wird.
Jason Jackson
14
@JSBangs, das ist scheiße, dass du eine absolut legitime Antwort gibst. Ihr Kommentar und die andere Antwort sprechen die gestellte Frage nicht an. Wenn Sie das Gefühl haben, eine bessere Antwort zu haben, geben Sie eine an. Die Antwort, die ich gegeben habe, zeigt jedoch, wie eine dokumentierte Klasse auf die gleiche Weise verwendet wird, wie andere Serialisierungsklassen diesen Code verwenden.
Jason Jackson
21
@JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) ist nicht undokumentiert.
Autodidakt
72

Verwenden Sie diese Überladung der CreateInstance-Methode:

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

Erstellt eine Instanz des angegebenen Typs mit dem Konstruktor, der den angegebenen Parametern am besten entspricht.

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

Nick
quelle
1
Diese Lösung vereinfacht das Problem zu stark. Was ist, wenn ich meinen Typ nicht kenne und sage "Erstelle einfach ein Objekt des Typs in dieser Typvariablen"?
Kamii
23

Beim Benchmarking war die Leistung (T)FormatterServices.GetUninitializedObject(typeof(T))langsamer. Gleichzeitig würden kompilierte Ausdrücke zu erheblichen Geschwindigkeitsverbesserungen führen, obwohl sie nur für Typen mit Standardkonstruktor funktionieren. Ich habe einen hybriden Ansatz gewählt:

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

Dies bedeutet, dass der Erstellungsausdruck effektiv zwischengespeichert wird und nur beim ersten Laden des Typs eine Strafe verursacht. Behandelt auch Werttypen auf effiziente Weise.

Nennen:

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

Beachten Sie, dass dies (T)FormatterServices.GetUninitializedObject(t)für die Zeichenfolge fehlschlägt. Daher ist eine spezielle Behandlung für Zeichenfolgen vorhanden, um leere Zeichenfolgen zurückzugeben.

nawfal
quelle
1
Es ist seltsam, wie ein Blick auf eine Zeile des Codes einer Person einen Tag retten kann. Danke mein Herr! Leistungsgründe haben mich zu Ihrem Beitrag geführt und der Trick ist erledigt :) FormatterServices- und Activator-Klassen sind im Vergleich zu kompilierten Ausdrücken unterdurchschnittlich, wie schade, dass man überall Aktivatoren findet.
Jmodrak
@nawfal In Bezug auf Ihre spezielle Behandlung für Zeichenfolgen weiß ich, dass es ohne diese spezielle Behandlung für Zeichenfolgen fehlschlagen würde, aber ich möchte nur wissen: Funktioniert es für alle anderen Typen?
Sнаđошƒаӽ
@ Sнаđошƒаӽ leider nein. Das angegebene Beispiel ist Barebone und .NET hat viele verschiedene Arten von Typen. Wenn Sie beispielsweise einen Delegatentyp übergeben, wie geben Sie ihm eine Instanz? Oder wenn der Konstruktor wirft, was können Sie dagegen tun? Viele verschiedene Möglichkeiten, damit umzugehen. Ich hatte seitdem die Antwort aktualisiert, um viel mehr Szenarien in meiner Bibliothek zu behandeln. Es wird vorerst nirgendwo veröffentlicht.
Nawfal
4

Gute Antworten, aber auf dem Dot Net Compact Framework unbrauchbar. Hier ist eine Lösung, die auf CF.Net funktioniert ...

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);
    }
}
Autodidakt
quelle
1
Auf diese Weise würde ich einen nicht standardmäßigen Konstruktor aufrufen. Ich bin mir nicht sicher, ob ich jemals ein Objekt erstellen möchte, ohne einen Konstruktor aufzurufen.
Rory MacLeod
2
Möglicherweise möchten Sie ein Objekt erstellen, ohne Konstruktoren aufzurufen, wenn Sie benutzerdefinierte Serialisierer schreiben.
Autodidact
1
Ja, das ist das genaue Anwendungsszenario, für das diese Frage war :)
Aistina
1
@Aistina Vielleicht könnten Sie diese Informationen der Frage hinzufügen? Die meisten Leute wären dagegen, Objekte zu erstellen, ohne ihre ctors anzurufen, und würden sich die Zeit nehmen, mit Ihnen darüber zu streiten, aber Ihr Anwendungsfall rechtfertigt dies tatsächlich, daher denke ich, dass es für die Frage selbst sehr relevant ist.
Julealgon