Tworzenie wystąpienia typu bez domyślnego konstruktora w C # przy użyciu odbicia

97

Weź następującą klasę jako przykład:

class Sometype
{
    int someValue;

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

Następnie chcę utworzyć instancję tego typu za pomocą odbicia:

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

Zwykle to zadziała, jednak ponieważ SomeTypenie zdefiniowano konstruktora bez parametrów, wywołanie Activator.CreateInstancewyrzuci wyjątek typu MissingMethodExceptionz komunikatem „ Nie zdefiniowano konstruktora bez parametrów dla tego obiektu. Czy istnieje alternatywny sposób tworzenia instancji tego typu? Byłoby trochę nieciekawe, gdyby dodać konstruktory bez parametrów do wszystkich moich klas.

Aistina
źródło
2
FormatterServices.GetUninitializedObjectnie pozwala na utworzenie niezainicjowanego ciągu. Możesz otrzymać wyjątek: System.ArgumentException: Uninitialized Strings cannot be created.pamiętaj o tym.
Bartosz Pierzchlewicz
Dzięki za ostrzeżenie, ale już osobno obsługuję stringi i podstawowe typy.
Aistina

Odpowiedzi:

143

Pierwotnie opublikowałem tę odpowiedź tutaj , ale tutaj jest przedruk, ponieważ nie jest to dokładnie to samo pytanie, ale ma tę samą odpowiedź:

FormatterServices.GetUninitializedObject()utworzy instancję bez wywoływania konstruktora. Znalazłem tę klasę, używając Reflectora i przeglądając niektóre z podstawowych klas serializacji .Net.

Przetestowałem to używając przykładowego kodu poniżej i wygląda na to, że działa świetnie:

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
źródło
Świetnie, wygląda na to, że właśnie tego potrzebuję. Zakładam, że niezainicjowany oznacza, że ​​cała jego pamięć zostanie ustawiona na zera? (Podobnie jak w przypadku tworzenia instancji struktur)
Aistina,
Niezależnie od wartości domyślnej dla każdego typu, będzie ona wartością domyślną. Więc obiekty będą miały wartość null, ints 0 itd. Myślę, że ma miejsce inicjalizacja na poziomie klasy, ale żaden konstruktor nie jest uruchamiany.
Jason Jackson
14
@JSBangs, to jest do bani, boisz całkowicie uzasadnioną odpowiedź. Twój komentarz i inna odpowiedź tak naprawdę nie odnoszą się do zadanego pytania. Jeśli uważasz, że masz lepszą odpowiedź, podaj jedną. Ale udzielona przeze mnie odpowiedź podkreśla, jak używać udokumentowanej klasy w taki sam sposób, w jaki inne klasy serializacji używają tego kodu.
Jason Jackson
21
@JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) nie jest nieudokumentowane.
Samouk
72

Użyj tego przeciążenia metody CreateInstance:

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

Tworzy wystąpienie określonego typu przy użyciu konstruktora, który najlepiej pasuje do określonych parametrów.

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

Nacięcie
źródło
1
Takie rozwiązanie nadmiernie upraszcza problem. Co się stanie, jeśli nie znam swojego typu i mówię „po prostu utwórz obiekt o zmiennej Type in this Type”?
kamii
23

Kiedy testowałem wydajność (T)FormatterServices.GetUninitializedObject(typeof(T)), było wolniej. W tym samym czasie skompilowane wyrażenia dałyby ci dużą poprawę szybkości, chociaż działają tylko dla typów z domyślnym konstruktorem. Przyjąłem podejście hybrydowe:

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

Oznacza to, że wyrażenie create jest skutecznie buforowane i wiąże się z karą tylko przy pierwszym ładowaniu typu. Obsługuje również typy wartości w efektywny sposób.

Nazwać:

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

Zauważ, że (T)FormatterServices.GetUninitializedObject(t)nie powiedzie się to dla stringa. W związku z tym istnieje specjalna obsługa łańcucha, aby zwrócić pusty łańcuch.

nawfal
źródło
1
To dziwne, jak spojrzenie na jedną linijkę czyjegoś kodu może zaoszczędzić dzień. Dziękuję Panu! Powody wydajnościowe zaprowadziły mnie do twojego postu i sztuczka jest zrobiona :) Klasy FormatterServices i Activator są gorsze w porównaniu do skompilowanych wyrażeń, a szkoda, że ​​wszędzie można znaleźć aktywatory.
jmodrak
@nawfal Jeśli chodzi o twoją specjalną obsługę łańcucha, wiem, że bez tej specjalnej obsługi nie powiedzie się, ale chcę tylko wiedzieć: czy będzie działać dla wszystkich innych typów?
Sнаđошƒаӽ
@ Sнаđошƒаӽ niestety nie. Podany przykład to komputery szkieletowe, a .NET ma wiele różnych typów typów. Na przykład zastanów się, czy jeśli przekażesz typ delegata, w jaki sposób nadasz mu instancję? Albo jeśli konstruktor rzuci, co możesz z tym zrobić? Wiele różnych sposobów radzenia sobie z tym. Od czasu odpowiedzi na to zaktualizowałem, aby obsłużyć o wiele więcej scenariuszy w mojej bibliotece. Na razie nie jest nigdzie publikowany.
nawfal
4

Dobre odpowiedzi, ale bezużyteczne w kompaktowej strukturze dot net. Oto rozwiązanie, które będzie działać na 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);
    }
}
Samouk
źródło
1
W ten sposób nazwałbym konstruktora innego niż domyślny. Nie jestem pewien, czy kiedykolwiek chciałbym stworzyć obiekt bez wywoływania żadnego konstruktora.
Rory MacLeod
2
Możesz chcieć utworzyć obiekt bez wywoływania konstruktorów, jeśli piszesz niestandardowe serializatory.
Samouk
1
Tak, to jest dokładnie taki scenariusz użycia, dla którego było to pytanie :)
Aistina
1
@Aistina Może mógłbyś dodać te informacje do pytania? Większość ludzi byłaby przeciwna tworzeniu obiektów bez wzywania ich lekarza i poświęcałaby czas na dyskusję z tobą na ten temat, ale twój przypadek użycia faktycznie to uzasadnia, więc myślę, że jest to bardzo istotne dla samego pytania.
julealgon