Lulus Sistem Instantiated. Jenis sebagai Parameter Tipe untuk Kelas Generik

182

Judulnya agak tidak jelas. Yang ingin saya ketahui adalah apakah ini mungkin:

string typeName = <read type name from somwhere>;
Type myType = Type.GetType(typeName);

MyGenericClass<myType> myGenericClass = new MyGenericClass<myType>();

Jelas, MyGenericClass digambarkan sebagai:

public class MyGenericClass<T>

Saat ini, kompiler mengeluh bahwa 'Tipe atau namespace' myType 'tidak dapat ditemukan. "Harus ada cara untuk melakukan ini.

Robert C. Barth
sumber
Generik! = Templat. Semua variabel tipe generik diselesaikan pada waktu kompilasi dan bukan pada saat runtime. Ini adalah salah satu situasi di mana tipe 'dinamis' dari 4.0 mungkin berguna.
1
@ Akan - dengan cara apa? Ketika digunakan dengan obat generik, di bawah CTP saat ini Anda pada dasarnya akan memanggil versi <object> (kecuali jika saya melewatkan trik ...)
Marc Gravell
@MarcGravell dapat Anda gunakan foo.Method((dynamic)myGenericClass)untuk menjalankan metode waktu berjalan, secara efektif pola pelokasi layanan untuk metode tipe ini kelebihan beban.
Chris Marisic
@ ChrisMarisic ya, untuk beberapa generik public void Method<T>(T obj)- trik yang saya gunakan lebih dari beberapa kali dalam 6 tahun terakhir sejak komentar itu; p
Marc Gravell
@ MarcGravell apakah ada cara untuk mengubah itu sehingga metode ini dipakai?
barlop

Jawaban:

220

Anda tidak dapat melakukan ini tanpa refleksi. Namun, Anda bisa melakukannya dengan refleksi. Ini contoh lengkapnya:

using System;
using System.Reflection;

public class Generic<T>
{
    public Generic()
    {
        Console.WriteLine("T={0}", typeof(T));
    }
}

class Test
{
    static void Main()
    {
        string typeName = "System.String";
        Type typeArgument = Type.GetType(typeName);

        Type genericClass = typeof(Generic<>);
        // MakeGenericType is badly named
        Type constructedClass = genericClass.MakeGenericType(typeArgument);

        object created = Activator.CreateInstance(constructedClass);
    }
}

Catatan: jika kelas generik Anda menerima beberapa jenis, Anda harus menyertakan koma ketika Anda menghilangkan nama-nama jenis, misalnya:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);
Jon Skeet
sumber
1
OK, ini bagus, tapi bagaimana cara memanggil metode yang dibuat? Lebih banyak refleksi?
Robert C. Barth
7
Nah, jika Anda bisa lolos dengan membuat tipe generik Anda mengimplementasikan antarmuka non-generik, Anda bisa menggunakan antarmuka itu. Atau, Anda bisa menulis metode generik Anda sendiri yang melakukan semua pekerjaan yang Anda ingin lakukan dengan generik, dan panggilan yang dengan refleksi.
Jon Skeet
1
Ya, saya tidak tahu bagaimana cara menggunakan yang dibuat jika satu-satunya info yang Anda miliki tentang jenis yang dikembalikan adalah dalam variabel tipe typeArgument? Bagi saya sepertinya Anda harus menggunakan variabel itu, tetapi Anda tidak tahu apa itu jadi saya tidak yakin apakah Anda bisa melakukannya dengan refleksi. Pertanyaan lain apakah objek tersebut adalah contoh dari tipe int jika Anda meneruskannya sebagai variabel objek ke dalam contoh mis a List <int> wil this work? Apakah variabel yang dibuat akan diperlakukan sebagai int?
theringostarrs
6
@ RobertC.Barth Anda juga dapat membuat objek "dibuat" dalam contoh ketik "dinamis" bukan "objek". Dengan begitu Anda dapat memanggil metode di atasnya, dan evaluasi akan ditangguhkan hingga runtime.
McGarnagle
4
@balanza: Anda menggunakan MakeGenericMethod.
Jon Skeet
14

Sayangnya tidak ada. Argumen umum harus dapat diselesaikan pada waktu kompilasi sebagai 1) tipe yang valid atau 2) parameter generik lainnya. Tidak ada cara untuk membuat instance generik berdasarkan nilai runtime tanpa palu besar menggunakan refleksi.

JaredPar
sumber
2

Beberapa tambahan cara menjalankan dengan kode gunting. Misalkan Anda memiliki kelas yang mirip dengan

public class Encoder() {
public void Markdown(IEnumerable<FooContent> contents) { do magic }
public void Markdown(IEnumerable<BarContent> contents) { do magic2 }
}

Misalkan saat runtime Anda memiliki FooContent

Jika Anda dapat mengikat pada waktu kompilasi yang Anda inginkan

var fooContents = new List<FooContent>(fooContent)
new Encoder().Markdown(fooContents)

Namun Anda tidak dapat melakukan ini saat runtime. Untuk melakukan ini saat runtime, Anda harus melakukannya di sepanjang baris:

var listType = typeof(List<>).MakeGenericType(myType);
var dynamicList = Activator.CreateInstance(listType);
((IList)dynamicList).Add(fooContent);

Untuk memohon secara dinamis Markdown(IEnumerable<FooContent> contents)

new Encoder().Markdown( (dynamic) dynamicList)

Perhatikan penggunaan dynamicdalam pemanggilan metode. Saat runtime dynamicListakan List<FooContent>(juga sedang IEnumerable<FooContent>) karena bahkan penggunaan dinamis masih di-rooting ke bahasa yang sangat diketik, binder waktu berjalan akan memilih Markdownmetode yang sesuai . Jika tidak ada pencocokan jenis yang tepat, ia akan mencari metode parameter objek dan jika tidak ada yang cocok dengan pengecualian binder runtime akan dimunculkan mengingatkan bahwa tidak ada metode yang cocok.

Yang jelas menarik kembali ke pendekatan ini adalah hilangnya besar jenis keselamatan pada waktu kompilasi. Namun demikian, kode di sepanjang garis ini akan memungkinkan Anda beroperasi dalam arti yang sangat dinamis yang pada saat runtime masih diketik sepenuhnya seperti yang Anda harapkan.

Chris Marisic
sumber
2

Persyaratan saya sedikit berbeda, tetapi mudah-mudahan akan membantu seseorang. Saya perlu membaca jenis dari konfigurasi dan instantiate jenis generik secara dinamis.

namespace GenericTest
{
    public class Item
    {
    }
}

namespace GenericTest
{
    public class GenericClass<T>
    {
    }
}

Akhirnya, ini adalah bagaimana Anda menyebutnya. Tentukan jenisnya dengan backtick .

var t = Type.GetType("GenericTest.GenericClass`1[[GenericTest.Item, GenericTest]], GenericTest");
var a = Activator.CreateInstance(t);
Master P
sumber
0

Jika Anda tahu jenis apa yang akan dilewati, Anda dapat melakukan ini tanpa refleksi. Pernyataan pergantian akan bekerja. Jelas, ini hanya akan bekerja dalam jumlah kasus yang terbatas, tetapi ini akan jauh lebih cepat daripada refleksi.

public class Type1 { }

public class Type2 { }

public class Generic<T> { }

public class Program
{
    public static void Main()
    {
        var typeName = nameof(Type1);

        switch (typeName)
        {
            case nameof(Type1):
                var type1 = new Generic<Type1>();
                // do something
                break;
            case nameof(Type2):
                var type2 = new Generic<Type2>();
                // do something
                break;
        }
    }
}
Todd Skelton
sumber
ini menjadi sangat cepat setelah Anda mulai berurusan dengan 100-an kelas.
michael g
0

Dalam cuplikan ini saya ingin menunjukkan cara membuat dan menggunakan daftar yang dibuat secara dinamis. Misalnya, saya menambahkan ke daftar dinamis di sini.

void AddValue<T>(object targetList, T valueToAdd)
{
    var addMethod = targetList.GetType().GetMethod("Add");
    addMethod.Invoke(targetList, new[] { valueToAdd } as object[]);
}

var listType = typeof(List<>).MakeGenericType(new[] { dynamicType }); // dynamicType is the type you want
var list = Activator.CreateInstance(listType);

AddValue(list, 5);

Demikian pula Anda dapat memanggil metode lain apa pun pada daftar.

EGN
sumber