Casting variabel menggunakan variabel Type

281

Dalam C # dapatkah saya melemparkan variabel tipe objek ke variabel tipe T di mana T didefinisikan dalam variabel Type?

theringostarrs
sumber
12
Tidak sepenuhnya pada topik, tetapi Anda tampaknya cukup kabur tentang apa arti "pemeran" yang mungkin merupakan ide yang baik untuk memahami dengan tepat apa tujuan dan semantik dari operator pemeran. Inilah awal yang baik: blogs.msdn.com/ericlippert/archive/2009/03/19/…
Eric Lippert
2
Saya pikir saya telah datang dengan sesuatu. Jika Anda memiliki Typevariabel, Anda bisa menggunakan refleksi untuk membuat turunan dari tipe itu. Dan kemudian Anda bisa menggunakan metode generik untuk mengembalikan tipe yang Anda inginkan dengan menyimpulkannya dari parameter jenis itu. Sayangnya, metode refleksi apa pun yang membuat turunan tipe akan memiliki tipe pengembalian object, sehingga CastByExamplemetode generik Anda juga akan digunakan object. Jadi benar-benar tidak ada cara untuk melakukan ini, dan bahkan jika ada, apa yang akan Anda lakukan dengan objek yang baru dilemparkan? Anda tidak dapat menggunakan metode atau apa pun karena Anda tidak tahu jenisnya.
Kyle Delaney
@KyleDelaney Terima kasih, saya sepenuhnya setuju! Ketika saya mencoba menjelaskan dalam jawaban saya, itu tidak benar-benar berguna untuk melemparkan sesuatu ke hal yang berbeda tanpa pada suatu titik mendefinisikan Jenis yang sebenarnya Anda gunakan. Inti dari semua tipe adalah pemeriksaan tipe waktu kompiler. Jika Anda hanya perlu melakukan panggilan pada objek, Anda dapat menggunakan objectatau dynamic. Jika Anda ingin memuat modul eksternal secara dinamis, Anda dapat meminta kelas berbagi antarmuka yang sama dan melemparkan objek ke sana. Jika Anda tidak mengontrol kode pihak ketiga, buat pembungkus kecil dan terapkan antarmuka itu.
Zyphrax

Jawaban:

203

Berikut adalah contoh pemeran dan orang yang insaf:

using System;

public T CastObject<T>(object input) {   
    return (T) input;   
}

public T ConvertObject<T>(object input) {
    return (T) Convert.ChangeType(input, typeof(T));
}

Edit:

Beberapa orang di komentar mengatakan bahwa jawaban ini tidak menjawab pertanyaan. Tapi jalurnya (T) Convert.ChangeType(input, typeof(T))menyediakan solusi. The Convert.ChangeTypeMetode mencoba untuk mengkonversi Obyek apapun untuk Type disediakan sebagai argumen kedua.

Sebagai contoh:

Type intType = typeof(Int32);
object value1 = 1000.1;

// Variable value2 is now an int with a value of 1000, the compiler 
// knows the exact type, it is safe to use and you will have autocomplete
int value2 = Convert.ChangeType(value1, intType);

// Variable value3 is now an int with a value of 1000, the compiler
// doesn't know the exact type so it will allow you to call any
// property or method on it, but will crash if it doesn't exist
dynamic value3 = Convert.ChangeType(value1, intType);

Aku sudah menulis jawaban dengan obat generik, karena saya pikir itu adalah sangat mungkin menandatangani kode bau ketika Anda ingin cor a somethinguntuk a something elsetanpa penanganan jenis yang sebenarnya. Dengan antarmuka yang tepat yang seharusnya tidak perlu 99,9% dari waktu. Mungkin ada beberapa kasus tepi ketika datang ke refleksi bahwa itu mungkin masuk akal, tetapi saya akan merekomendasikan untuk menghindari kasus-kasus itu.

Edit 2:

Beberapa tips tambahan:

  • Cobalah untuk menjaga kode Anda seaman mungkin. Jika kompiler tidak mengetahui jenisnya, maka ia tidak dapat memeriksa apakah kode Anda benar dan hal-hal seperti pelengkapan otomatis tidak akan berfungsi. Secara sederhana: jika Anda tidak dapat memprediksi tipe pada waktu kompilasi, lalu bagaimana kompiler dapat melakukannya ?
  • Jika kelas yang Anda gunakan mengimplementasikan antarmuka umum , Anda bisa memberikan nilai ke antarmuka itu. Kalau tidak, pertimbangkan untuk membuat antarmuka Anda sendiri dan minta kelas mengimplementasikan antarmuka itu.
  • Jika Anda bekerja dengan perpustakaan eksternal yang Anda impor secara dinamis, maka periksa juga antarmuka umum. Kalau tidak, pertimbangkan untuk membuat kelas pembungkus kecil yang mengimplementasikan antarmuka.
  • Jika Anda ingin melakukan panggilan pada objek, tetapi tidak peduli tentang jenisnya, maka simpan nilai dalam variabel objectatau dynamic.
  • Generik dapat menjadi cara yang bagus untuk membuat kode yang dapat digunakan kembali yang berlaku untuk banyak jenis berbeda, tanpa harus mengetahui jenis persisnya.
  • Jika Anda mengalami masalah maka pertimbangkan pendekatan atau refactor kode yang berbeda. Apakah kode Anda benar-benar harus dinamis? Apakah harus memperhitungkan jenis apa pun yang ada?
Zyphrax
sumber
145
Saya tidak tahu bagaimana ini membantu OP. Dia memiliki variabel tipe, bukan Tseperti itu.
nawfal
12
@nawfal, pada dasarnya garis Convert.ChangeType(input, typeof(T));memberikan solusi. Anda dapat dengan mudah mengganti typeof(T)dengan variabel tipe yang ada. Solusi yang lebih baik (jika mungkin) adalah untuk mencegah tipe dinamis secara bersamaan.
Zyphrax
59
@Zyphrax, tidak, itu masih membutuhkan gips Tyang tidak tersedia.
nawfal
4
Saya tahu objek yang dihasilkan benar-benar bertipe Ttetapi Anda hanya mendapatkan objectsebagai referensi. hmm, saya menemukan pertanyaan yang menarik dalam premis bahwa OP hanya memiliki Typevariabel dan tidak ada info lainnya. Seolah-olah metode tanda tangan adalah Convert(object source, Type destination):) Namun saya mendapatkan poin Anda
nawfal
10
Bagaimana ini solusi untuk pertanyaan ini? Saya punya masalah yang sama dan saya tidak memiliki <T> generik. Saya hanya memiliki variabel tipe.
Nuri Tasdemir
114

Jawaban lain tidak menyebutkan tipe "dinamis". Jadi, untuk menambahkan satu jawaban lagi, Anda dapat menggunakan tipe "dinamis" untuk menyimpan objek yang dihasilkan tanpa harus membuang objek yang dikonversi dengan tipe statis.

dynamic changedObj = Convert.ChangeType(obj, typeVar);
changedObj.Method();

Perlu diingat bahwa dengan menggunakan "dinamis" kompiler melewati pemeriksaan tipe statis yang dapat memperkenalkan kemungkinan kesalahan runtime jika Anda tidak hati-hati.

maulik13
sumber
19
Ini jawaban yang benar. Tanpa jenis kata kunci dinamis (ubahObj) adalah "objek". Dengan kata kunci dinamis, kata kunci itu berfungsi dengan sempurna dan typeof (changedObject) dengan benar mencerminkan tipe yang sama dengan typeVar. Selain itu Anda tidak perlu (T) melakukan cast yang tidak dapat Anda lakukan jika Anda tidak tahu tipenya.
bergegas
5
Saya mendapat pengecualian "Objek harus mengimplementasikan IConvertible" saat menggunakan solusi ini. Ada bantuan?
Nuri Tasdemir
@NuriTasdemir Sulit dikatakan, tapi saya yakin konversi yang Anda lakukan tidak mungkin tanpa IConvertible. Apa jenis yang terlibat dalam konversi Anda?
maulik13
Sementara ini berhasil, ada penalti kinerja dengan menggunakan dinamika. Saya akan merekomendasikan untuk tidak menggunakannya kecuali jika Anda bekerja dengan runtimes lain (yang merupakan dinamika yang dirancang untuk).
Bolo
19

Berikut adalah metode saya untuk melemparkan objek tetapi tidak ke variabel tipe generik, bukan ke System.Typedinamis:

Saya membuat ekspresi lambda pada saat run-time menggunakan System.Linq.Expressions, tipe Func<object, object>, yang membuka kotak inputnya, melakukan konversi tipe yang diinginkan kemudian memberikan hasilnya kotak. Yang baru diperlukan tidak hanya untuk semua jenis yang dicasting, tetapi juga untuk jenis yang dicasting (karena langkah unboxing). Menciptakan ekspresi ini sangat memakan waktu, karena refleksi, kompilasi dan metode dinamis yang dilakukan di bawah tenda. Untungnya sekali dibuat, ekspresi dapat dipanggil berulang kali dan tanpa overhead tinggi, jadi saya cache masing-masing.

private static Func<object, object> MakeCastDelegate(Type from, Type to)
{
    var p = Expression.Parameter(typeof(object)); //do not inline
    return Expression.Lambda<Func<object, object>>(
        Expression.Convert(Expression.ConvertChecked(Expression.Convert(p, from), to), typeof(object)),
        p).Compile();
}

private static readonly Dictionary<Tuple<Type, Type>, Func<object, object>> CastCache
= new Dictionary<Tuple<Type, Type>, Func<object, object>>();

public static Func<object, object> GetCastDelegate(Type from, Type to)
{
    lock (CastCache)
    {
        var key = new Tuple<Type, Type>(from, to);
        Func<object, object> cast_delegate;
        if (!CastCache.TryGetValue(key, out cast_delegate))
        {
            cast_delegate = MakeCastDelegate(from, to);
            CastCache.Add(key, cast_delegate);
        }
        return cast_delegate;
    }
}

public static object Cast(Type t, object o)
{
    return GetCastDelegate(o.GetType(), t).Invoke(o);
}

Perhatikan bahwa ini bukan sihir. Casting tidak terjadi dalam kode, seperti halnya dengan dynamickata kunci, hanya data yang mendasari objek yang akan dikonversi. Pada waktu kompilasi kita masih harus bersusah payah mencari tahu dengan tepat apa jenis objek kita, membuat solusi ini tidak praktis. Saya menulis ini sebagai peretasan untuk memanggil operator konversi yang ditentukan oleh jenis arbitrer, tetapi mungkin seseorang di luar sana dapat menemukan use case yang lebih baik.

balage
sumber
2
Membutuhkanusing System.Linq.Expressions;
Aaron D
4
Bagi saya ini mengalami masalah yang sama dengan jawaban Zyphrax. Saya tidak dapat memanggil metode pada objek yang dikembalikan karena masih tipe "objek". Apakah saya menggunakan metodenya ("a" di bawah) atau metode Anda ("b" di bawah) saya mendapatkan kesalahan yang sama pada (t) pemeran - "'t' adalah variabel tetapi digunakan seperti tipe.Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla
@muusbolla Jawaban asli Zyphrax menggunakan variabel generik dan tipe, bukan Type. Anda tidak dapat melakukan casting menggunakan sintaks casting normal jika yang Anda miliki hanyalah objek Type. Jika Anda ingin dapat menggunakan objek sebagai beberapa tipe T pada waktu kompilasi, bukan runtime, Anda perlu melemparkannya menggunakan variabel tipe atau hanya nama tipe yang sebenarnya. Anda dapat melakukan yang pertama menggunakan jawaban Zaphrax.
Ashley
8

Mengesampingkan tinju dan unboxing untuk kesederhanaan, tidak ada tindakan runtime khusus yang terlibat dalam casting di sepanjang hierarki warisan. Ini sebagian besar waktu kompilasi. Pada dasarnya, seorang pemeran memberi tahu kompiler untuk memperlakukan nilai variabel sebagai tipe lain.

Apa yang bisa Anda lakukan setelah para pemain? Anda tidak tahu jenisnya, jadi Anda tidak akan dapat memanggil metode apa pun di dalamnya. Tidak akan ada hal khusus yang dapat Anda lakukan. Secara khusus, ini dapat berguna hanya jika Anda mengetahui jenis yang mungkin pada waktu kompilasi, melemparkannya secara manual dan menangani setiap kasus secara terpisah dengan ifpernyataan:

if (type == typeof(int)) {
    int x = (int)obj;
    DoSomethingWithInt(x);
} else if (type == typeof(string)) {
    string s = (string)obj;
    DoSomethingWithString(s);
} // ...
Mehrdad Afshari
sumber
1
Bisakah Anda jelaskan lebih jelas sehubungan dengan pertanyaan saya?
theringostarrs
Apa yang saya coba jelaskan adalah, apa yang dapat Anda lakukan setelah itu? Anda tidak dapat berbuat banyak sebagai C # compiler membutuhkan mengetik statis untuk dapat melakukan hal yang berguna dengan objek
Mehrdad Afshari
Kamu benar. Saya tahu tipe yang diharapkan dari dua variabel yang dikirim ke metode sebagai tipe 'objek'. Saya ingin menggunakan tipe yang diharapkan yang disimpan dalam variabel, dan menambahkannya ke koleksi. Jauh lebih mudah untuk bercabang pada tipe dan mencoba melakukan cast dan catch error yang normal.
theringostarrs
4
Jawaban Anda baik, tetapi hanya untuk rewel, saya perhatikan bahwa para pemain tidak pernah mempengaruhi variabel . Tidak pernah sah untuk melemparkan variabel ke variabel jenis lain; tipe variabel invarian dalam C #. Anda hanya bisa melemparkan nilai yang disimpan dalam variabel ke tipe lain.
Eric Lippert
Apakah pengantar C # 4.0 tentang pengetikan dinamis mengubah jawaban ini?
Daniel T.
6

Bagaimana Anda bisa melakukan itu? Anda memerlukan variabel atau bidang tipe T di mana Anda dapat menyimpan objek setelah pemain, tetapi bagaimana Anda bisa memiliki variabel atau bidang tersebut jika Anda tahu T hanya saat runtime? Jadi, tidak, itu tidak mungkin.

Type type = GetSomeType();
Object @object = GetSomeObject();

??? xyz = @object.CastTo(type); // How would you declare the variable?

xyz.??? // What methods, properties, or fields are valid here?
Daniel Brückner
sumber
3
Jika Anda menggunakan kelas generik, yang mendefinisikan metode dengan nilai pengembalian tipe T, Anda bisa melakukannya. Misalnya mengurai string ke instance T dan mengembalikannya.
Oliver Friedrich
7
Untungnya ini bukan jawaban yang benar. Lihat jawaban maulik13.
bergegas
3
Di mana dalam nama Surga Anda menemukan CastTometode Object?
ProfK
3

Ketika datang untuk casting ke tipe Enum:

private static Enum GetEnum(Type type, int value)
    {
        if (type.IsEnum)
            if (Enum.IsDefined(type, value))
            {
                return (Enum)Enum.ToObject(type, value);
            }

        return null;
    }

Dan Anda akan menyebutnya seperti itu:

var enumValue = GetEnum(typeof(YourEnum), foo);

Ini penting bagi saya jika mendapatkan nilai atribut Deskripsi beberapa tipe enum berdasarkan nilai int:

public enum YourEnum
{
    [Description("Desc1")]
    Val1,
    [Description("Desc2")]
    Val2,
    Val3,
}

public static string GetDescriptionFromEnum(Enum value, bool inherit)
    {
        Type type = value.GetType();

        System.Reflection.MemberInfo[] memInfo = type.GetMember(value.ToString());

        if (memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), inherit);
            if (attrs.Length > 0)
                return ((DescriptionAttribute)attrs[0]).Description;
        }

        return value.ToString();
    }

lalu:

string description = GetDescriptionFromEnum(GetEnum(typeof(YourEnum), foo));
string description2 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum2), foo2));
string description3 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum3), foo3));

Atau (pendekatan yang lebih baik), casting seperti itu bisa terlihat seperti itu:

 private static T GetEnum<T>(int v) where T : struct, IConvertible
    {
        if (typeof(T).IsEnum)
            if (Enum.IsDefined(typeof(T), v))
            {
                return (T)Enum.ToObject(typeof(T), v);
            }

        throw new ArgumentException(string.Format("{0} is not a valid value of {1}", v, typeof(T).Name));
    }
krzyski
sumber
1

Setelah tidak menemukan apa pun untuk berkeliling "Objek harus menerapkan IConvertible" pengecualian ketika menggunakan jawaban Zyphrax (kecuali untuk mengimplementasikan antarmuka). Saya mencoba sesuatu yang sedikit tidak konvensional dan bekerja untuk situasi saya.

Menggunakan paket nuget Newtonsoft.Json ...

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);
Singkat
sumber
1

Harm, masalahnya adalah Anda tidak memiliki T.

Anda hanya memiliki variabel Type.

Petunjuk ke MS, jika Anda bisa melakukan sesuatu seperti

TryCast<typeof(MyClass)>

jika mau menyelesaikan semua masalah kita.

pengguna2825546
sumber
0

Saya tidak akan pernah mengerti mengapa Anda perlu hingga 50 reputasi untuk meninggalkan komentar, tetapi saya hanya harus mengatakan bahwa jawaban @Curt persis seperti yang saya cari dan semoga orang lain.

Dalam contoh saya, saya memiliki ActionFilterAttribute yang saya gunakan untuk memperbarui nilai-nilai dokumen patch json. Saya tidak tahu apa model T untuk dokumen tambalan yang harus saya masukkan bersambung & deserialize ke dokumen JsonPatchDocument, modifikasi, lalu karena saya punya jenisnya, sambungkan & deserialisasi kembali ke jenis itu lagi.

Type originalType = //someType that gets passed in to my constructor.

var objectAsString = JsonConvert.SerializeObject(myObjectWithAGenericType);
var plainPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument>(objectAsString);

var plainPatchDocumentAsString= JsonConvert.SerializeObject(plainPatchDocument);
var modifiedObjectWithGenericType = JsonConvert.DeserializeObject(plainPatchDocumentAsString, originalType );
Leye Eltee Taiwo
sumber
-1
public bool TryCast<T>(ref T t, object o)
{
    if (
        o == null
        || !typeof(T).IsAssignableFrom(o.GetType())
        )
        return false;
    t = (T)o;
    return true;
}
pengguna2008563
sumber
2
Bisakah Anda menunjukkan bagaimana jawaban ini berbeda dari jawaban lainnya dan di mana solusi ini tepat?
Klaus Gütter
-2

lebih bersih:

    public static bool TryCast<T>(ref T t, object o)
    {
        if (!(o is T))
        {
            return false;
        }

        t = (T)o;
        return true;
    }
Salom yang membahayakan
sumber
-2

Jika Anda perlu melemparkan objek saat runtime tanpa mengetahui tipe tujuan, Anda dapat menggunakan refleksi untuk membuat konverter dinamis.

Ini adalah versi yang disederhanakan (tanpa metode caching yang dihasilkan):

    public static class Tool
    {
            public static object CastTo<T>(object value) where T : class
            {
                return value as T;
            }

            private static readonly MethodInfo CastToInfo = typeof (Tool).GetMethod("CastTo");

            public static object DynamicCast(object source, Type targetType)
            {
                return CastToInfo.MakeGenericMethod(new[] { targetType }).Invoke(null, new[] { source });
            }
    }

maka Anda dapat menyebutnya:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
marianop
sumber