Membuat contoh tipe tanpa konstruktor default di C # menggunakan refleksi

98

Ambil kelas berikut sebagai contoh:

class Sometype
{
    int someValue;

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

Saya kemudian ingin membuat contoh jenis ini menggunakan refleksi:

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

Biasanya ini akan berfungsi, namun karena SomeTypebelum mendefinisikan konstruktor tanpa parameter, panggilan ke Activator.CreateInstanceakan memunculkan pengecualian tipe MissingMethodExceptiondengan pesan " Tidak ada konstruktor parameterless yang ditentukan untuk objek ini. " Apakah ada cara alternatif untuk tetap membuat turunan dari jenis ini? Akan agak menyebalkan untuk menambahkan konstruktor tanpa parameter ke semua kelas saya.

Aistina
sumber
2
FormatterServices.GetUninitializedObjecttidak memungkinkan untuk membuat string yang tidak diinisialisasi. Anda mungkin mendapatkan pengecualian: System.ArgumentException: Uninitialized Strings cannot be created.Harap perhatikan hal ini.
Bartosz Pierzchlewicz
Terima kasih atas perhatiannya, tetapi saya sudah menangani string dan tipe dasar secara terpisah.
Aistina

Jawaban:

143

Saya awalnya memposting jawaban ini di sini , tetapi ini adalah cetakan ulang karena ini bukan pertanyaan yang persis sama tetapi memiliki jawaban yang sama:

FormatterServices.GetUninitializedObject()akan membuat sebuah instance tanpa memanggil konstruktor. Saya menemukan kelas ini dengan menggunakan Reflektor dan menggali melalui beberapa kelas serialisasi inti .Net.

Saya mengujinya menggunakan kode contoh di bawah dan sepertinya berfungsi dengan baik:

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
sumber
Luar biasa, sepertinya itulah yang saya butuhkan. Saya berasumsi berarti tidak diinisialisasi semua memorinya akan disetel ke nol? (Mirip dengan bagaimana struct dibuat)
Aistina
Apapun nilai default untuk setiap jenis akan menjadi default. Jadi objek akan menjadi null, ints 0, dll. Saya berpikir bahwa setiap inisialisasi tingkat kelas terjadi, tetapi tidak ada konstruktor yang dijalankan.
Jason Jackson
14
@JSBangs, Itu menyebalkan Anda memberikan jawaban yang sangat sah. Komentar Anda dan jawaban lainnya sebenarnya tidak menjawab pertanyaan yang diajukan. Jika Anda merasa memiliki jawaban yang lebih baik, berikan jawaban. Tetapi jawaban yang saya berikan menyoroti bagaimana menggunakan kelas yang didokumentasikan dengan cara yang sama kelas serialisasi lain menggunakan kode ini.
Jason Jackson
21
@JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) tidak tidak berdokumen.
Autodidak
72

Gunakan kelebihan metode CreateInstance ini:

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

Membuat instance dari jenis yang ditentukan menggunakan konstruktor yang paling cocok dengan parameter yang ditentukan.

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

Nick
sumber
1
Solusi ini menyederhanakan masalah. Bagaimana jika saya tidak tahu tipe saya dan saya berkata "buat saja objek dari Type dalam variabel Type ini"?
kamii
23

Ketika saya membandingkan kinerjanya (T)FormatterServices.GetUninitializedObject(typeof(T))lebih lambat. Pada saat yang sama, ekspresi terkompilasi akan memberi Anda peningkatan kecepatan yang luar biasa meskipun hanya berfungsi untuk tipe dengan konstruktor default. Saya mengambil pendekatan hybrid:

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

Ini berarti ekspresi create di-cache secara efektif dan dikenakan penalti hanya saat pertama kali jenis dimuat. Akan menangani jenis nilai juga dengan cara yang efisien.

Sebut saja:

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

Perhatikan bahwa (T)FormatterServices.GetUninitializedObject(t)akan gagal untuk string. Oleh karena itu penanganan khusus untuk string dilakukan untuk mengembalikan string kosong.

nawfal
sumber
1
Sungguh aneh bagaimana melihat satu baris kode seseorang dapat menghemat satu hari. Terima kasih Pak! Alasan kinerja membawa saya ke posting Anda dan triknya selesai :) Kelas FormatterServices dan Activator berkinerja buruk dibandingkan dengan ekspresi yang dikompilasi, sayang sekali orang menemukan Activators di sekitar tempat itu.
jmodrak
@nawfal Mengenai penanganan khusus Anda untuk string, saya tahu ini akan gagal untuk string tanpa penanganan khusus ini, tetapi saya hanya ingin tahu: apakah ini akan berfungsi untuk semua jenis lainnya?
Sнаđошƒаӽ
@ Sнаđошƒаӽ sayangnya tidak. Contoh yang diberikan adalah barebone dan .NET memiliki banyak tipe yang berbeda. Misalnya, pertimbangkan, jika Anda lulus tipe delegasi, bagaimana Anda akan memberinya contoh? Atau jika konstruktor melempar apa yang dapat Anda lakukan? Banyak cara berbeda untuk mengatasinya. Saya sejak menjawab ini diperbarui untuk menangani lebih banyak skenario di perpustakaan saya. Itu tidak dipublikasikan di mana pun untuk saat ini.
nawfal
4

Jawaban bagus tetapi tidak dapat digunakan pada kerangka kompak dot net. Berikut adalah solusi yang akan bekerja pada 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);
    }
}
Otodidak
sumber
1
Ini adalah cara saya memanggil konstruktor non-default. Saya tidak yakin saya ingin membuat objek tanpa memanggil konstruktor sama sekali.
Rory MacLeod
2
Anda mungkin ingin membuat objek tanpa memanggil konstruktor jika Anda menulis serializers kustom.
Autodidak
1
Yup, itulah skenario kasus penggunaan yang tepat untuk pertanyaan ini :)
Aistina
1
@Aistina Mungkin Anda dapat menambahkan informasi ini ke pertanyaan? Kebanyakan orang akan menentang pembuatan objek tanpa menghubungi pemberi mereka dan akan meluangkan waktu untuk berdebat dengan Anda tentang hal itu, tetapi kasus penggunaan Anda sebenarnya membenarkannya, jadi saya pikir ini sangat relevan dengan pertanyaan itu sendiri.
julealgon