Cara membuat instance objek baru dari Tipe

747

Seseorang mungkin tidak selalu mengetahui Typeobjek pada saat kompilasi, tetapi mungkin perlu membuat instance dari Type.

Bagaimana Anda mendapatkan instance objek baru dari Type?

tags2k
sumber

Jawaban:

896

The Activatorkelas dalam akar Systemnamespace cukup kuat.

Ada banyak kelebihan untuk melewatkan parameter ke konstruktor dan semacamnya. Lihat dokumentasi di:

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

atau (jalur baru)

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

Berikut ini beberapa contoh sederhana:

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

ObjectType instance = (ObjectType)Activator.CreateInstance("MyAssembly","MyNamespace.ObjectType");
Karl Seguin
sumber
21
Senang akhirnya menemukan ini, tetapi panggilan kedua tidak sepenuhnya benar, melewatkan kutipan dan parm terbalik, seharusnya: ObjectType instance = (ObjectType) Activator.CreateInstance ("MyAssembly", "MyNamespace.ObjectType");
kevinc
10
Anda perlu memanggil 'Unwrap ()' untuk mendapatkan jenis objek aktual yang Anda inginkan: Instance ConcreteType = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Ε Г И І И О
4
Bagaimana ObjectType instancecocok dengan kondisi OP "Seseorang mungkin tidak selalu tahu jenis objek pada waktu kompilasi"? : P
Martin Schneider
@ MA-Maddin baiklah kalau begitu object instance = Activator.CreateInstance(...);,.
BrainSlugs83
1
Adakah yang tahu bagaimana melakukan ini di .NET Core? Metode Unwrap tidak tersedia pada objek.
Justin
145
ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);

The Activatorkelas memiliki varian generik yang membuat ini sedikit lebih mudah:

ObjectType instance = Activator.CreateInstance<ObjectType>();
Konrad Rudolph
sumber
8
@ Kevin Tentu saja. Operasi semacam itu tidak dapat bekerja dalam bahasa yang diketik secara statis karena tidak masuk akal. Anda tidak dapat memanggil metode pada objek dengan tipe yang tidak dikenal. Sementara itu (= sejak menulis jawaban ini) C # telah mendapatkan dynamickonstruk yang memang mengizinkan konstruk tersebut tetapi untuk sebagian besar tujuan jawaban ini masih mencakupnya.
Konrad Rudolph
1
@KonradRudolph Tidak sepenuhnya benar. Pertama-c # tidak memungkinkan Anda untuk membuat jenis baru saat runtime. Anda tidak bisa memanggil mereka dengan cara yang aman secara statis . Jadi ya, Anda setengah benar. Tetapi lebih realistis Anda membutuhkan ini ketika Anda memuat rakitan saat runtime, yang berarti tipe tidak diketahui pada waktu kompilasi. C # akan sangat terbatas jika Anda tidak bisa melakukan ini. Maksud saya, Anda baru saja membuktikannya sendiri: bagaimana lagi metode Activator yang menggunakan instance-type berfungsi? Ketika MS menulis kelas Activator mereka tidak memiliki pengetahuan waktu kompilasi dari setiap jenis pengguna masa depan akan menulis.
AnorZaken
1
@AnorZaken Komentar saya tidak mengatakan apa-apa tentang membuat tipe saat runtime. Tentu saja Anda dapat melakukannya, tetapi Anda tidak dapat menggunakannya secara statis dalam konteks yang sama (Anda tentu saja dapat meng-host program yang dikompilasi secara statis penuh). Itu semua komentar saya katakan.
Konrad Rudolph
@KonradRudolph Ah maaf, disalahtafsirkan "operasi semacam itu" berarti instantiating jenis yang hanya dikenal saat runtime; alih-alih makna menggunakan tipe runtime sebagai parameter tipe generik.
AnorZaken
1
@AnorZaken - secara teknis Anda bisa membuat tipe baru saat runtime DAN memanggil metode dengan cara yang aman secara statis jika tipe baru Anda mengimplementasikan antarmuka yang dikenal atau mewarisi kelas dasar yang dikenal. - Salah satu dari pendekatan itu akan memberi Anda kontrak statis untuk objek runtime yang Anda buat.
BrainSlugs83
132

Ekspresi terkompilasi adalah cara terbaik! (agar kinerja berulang kali membuat instance dalam runtime).

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

X x = YCreator();

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

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

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

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

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

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

Kode lengkap:

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) {}
}
Serj-Tm
sumber
18
+1 untuk semua statistik! Saya tidak benar-benar membutuhkan kinerja semacam ini saat ini, tetapi masih sangat menarik. :)
AnorZaken
1
Juga ada TypeDescriptor.CreateInstance (lihat stackoverflow.com/a/17797389/1242 ) yang bisa lebih cepat jika digunakan dengan TypeDescriptor.AddProvider
Lars Truijens
2
Apakah ini masih berguna ketika Anda tidak tahu apa jenisnya Xsaat runtime?
ajeh
1
@ Ya Ya. Ubah typeof (T) menjadi Type.GetType (..).
Serj-Tm
3
@ Serj-Tm Tidak, itu tidak akan berfungsi jika tipe X adalah runtime Type.
NetMage
47

Salah satu implementasi dari masalah ini adalah mencoba memanggil konstruktor tanpa parameter dari Tipe:

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

Berikut adalah pendekatan yang sama, terkandung dalam metode generik:

public static T GetNewObject<T>()
{
    try
    {
        return (T)typeof(T).GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return default(T);
    }
}
tags2k
sumber
15
Pemrograman yang didorong oleh pengecualian? Ini terlihat seperti implementasi yang sangat buruk ketika Anda bisa merefleksikan tipe untuk menentukan konstruktor.
Firoso
16

Cukup sederhana. Asumsikan bahwa nama kelas Anda Cardan namespace adalah Vehicles, lalu kirimkan parameter Vehicles.Caryang mengembalikan objek bertipe Car. Seperti ini, Anda dapat membuat instance kelas apa pun secara dinamis.

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

Jika Nama Sepenuhnya Memenuhi Syarat Anda (yaitu, Vehicles.Cardalam hal ini) di majelis lain, itu Type.GetTypeakan menjadi nol. Dalam kasus seperti itu, Anda memiliki perulangan melalui semua majelis dan menemukan Type. Untuk itu Anda bisa menggunakan kode di bawah ini

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

Dan Anda bisa mendapatkan instance dengan memanggil metode di atas.

object objClassInstance = GetInstance("Vehicles.Car");
Sarath Avanavu
sumber
Dalam kasus kedua Anda (perakitan eksternal), Anda bisa memasukkan "Kendaraan. Mobil, Lainnya" ke metode pertama Anda dan itu akan berhasil. Tentunya OtherAssembly adalah nama majelis tempat tinggalnya.
danmiser
2
@danmiser Itu perlu pengkodean nama perakitan. Untuk menerapkan fleksibilitas, saya memeriksa null dan kodenya bekerja dengan cara yang dinamis :)
Sarath Avanavu
14

Jika ini untuk sesuatu yang akan disebut banyak dalam contoh aplikasi, itu jauh lebih cepat untuk mengkompilasi dan menyimpan kode dinamis daripada menggunakan aktivator atau ConstructorInfo.Invoke(). Dua opsi mudah untuk kompilasi dinamis dikompilasi dengan Linq Expressions atau beberapa ILopcodeDynamicMethod sederhana dan . Either way, perbedaannya sangat besar ketika Anda mulai masuk ke loop ketat atau beberapa panggilan.

Tom Mayfield
sumber
11

Bukankah generiknya T t = new T();berfungsi?

Brady Moritz
sumber
9
Sebenarnya, itu akan di kelas generik / metode, tetapi tidak untuk "Jenis" yang diberikan.
Brady Moritz
Diasumsikan bahwa tipe T memiliki batasan 'baru ()'.
Rob Von Nesselrode
10

Jika Anda ingin menggunakan konstruktor default maka solusi menggunakan yang System.Activatordisajikan sebelumnya mungkin yang paling nyaman. Namun, jika jenis tidak memiliki konstruktor default atau Anda harus menggunakan yang non-default, maka opsi adalah menggunakan refleksi atau System.ComponentModel.TypeDescriptor. Dalam hal refleksi, cukup untuk mengetahui hanya nama tipe (dengan namespace-nya).

Contoh menggunakan refleksi:

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

Contoh menggunakan 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 }
    );
BSharp
sumber
args[]persis apa yang saya datang ke pertanyaan ini untuk menemukan, terima kasih!
Chad
10

Tanpa menggunakan Refleksi:

private T Create<T>() where T : class, new()
{
    return new T();
}
meagar
sumber
5
Bagaimana ini berguna? Anda harus mengetahui tipe yang sudah memanggil metode itu, dan jika Anda tahu jenisnya, Anda dapat membangunnya tanpa metode khusus.
Kyle Delaney
Jadi T dapat bervariasi saat runtime. Berguna jika Anda bekerja dengan Jenis Deríved.
T baru (); akan gagal jika T bukan tipe referensi dengan konstruktor tanpa parameter, Metode ini menggunakan batasan untuk memastikan T adalah tipe referensi dan memiliki konstruktor.
3
Bagaimana T dapat bervariasi pada saat runtime? Tidakkah Anda harus tahu T pada waktu desain untuk menelepon Buat <>?
Kyle Delaney
Jika Anda bekerja dengan kelas generik dan antarmuka di pabrik, jenis yang mengimplementasikan antarmuka harus di-instanciated dapat bervariasi.
8

Dengan masalah ini, Activator akan bekerja ketika ada parameter tanpa parameter. Jika ini merupakan kendala, pertimbangkan untuk menggunakan

System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()
Thulani Chivandikwa
sumber
5
public AbstractType New
{
    get
    {
        return (AbstractType) Activator.CreateInstance(GetType());
    }
}
vikram nayak
sumber
4

Saya dapat menemukan pertanyaan ini karena saya ingin menerapkan metode CloneObject sederhana untuk kelas arbitrer (dengan konstruktor default)

Dengan metode generik Anda dapat meminta jenis mengimplementasikan 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

Dengan non-generik anggap jenis memiliki konstruktor default dan menangkap pengecualian jika tidak.

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
Darrel Lee
sumber