GetType () bisa berbohong?

94

Berdasarkan pertanyaan berikut yang diajukan beberapa hari yang lalu di SO: GetType () dan polimorfisme serta membaca jawaban Eric Lippert , saya mulai berpikir jika menjadikan GetType()tidak virtual benar-benar memastikan bahwa suatu objek tidak dapat berbohong tentangnya Type.

Secara spesifik, jawaban Eric menyatakan sebagai berikut:

Perancang kerangka tidak akan menambahkan fitur yang sangat berbahaya seperti membiarkan objek berbohong tentang tipenya hanya untuk membuatnya konsisten dengan tiga metode lain pada tipe yang sama.

Sekarang pertanyaannya adalah: dapatkah saya membuat sebuah objek yang benar - benar berbohong tentang tipenya tanpa membuatnya terlihat jelas? Saya mungkin sangat salah di sini dan saya ingin klarifikasi jika itu masalahnya, tetapi pertimbangkan kode berikut:

public interface IFoo
{
    Type GetType();
}

Dan dua implementasi berikut dari antarmuka tersebut:

public class BadFoo : IFoo
{
    Type IFoo.GetType()
    {
        return typeof(int);
    }
}

public class NiceFoo : IFoo
{
}

Kemudian jika Anda menjalankan program sederhana berikut:

static void Main(string[] args)
{
    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    Console.ReadLine();
}

Benar saja badFookeluaran yang salah Type.

Sekarang saya tidak tahu apakah ini memiliki implikasi serius berdasarkan Eric yang menggambarkan perilaku ini sebagai " fitur yang sangat berbahaya ", tetapi dapatkah pola ini menimbulkan ancaman yang dapat dipercaya?

Diantara
sumber
3
judul dan topik yang menarik!
David
43
IFoo.GetTypedan object.GetTypebukan hal yang sama jadi tidak ada hal buruk yang terjadi di sini kecuali gaya yang buruk. Sunting: Umumnya GetTypeakan dipanggil pada beberapa objek yang tidak dikenal pada waktu kompilasi, dalam banyak kasus objectdan bukan antarmuka yang cerdik. :)
leppie
4
Gelar Tuan, membuat hariku.
Soner Gönül
5
Anda baru saja memperkenalkan anggota baru yang juga dipanggil GetType, dengan tanda tangan yang sama. Itu tidak berhubungan dengan GetTypemetode yang penting. Anda juga bisa membuat metode contoh publik yang menyembunyikan metode yang relevan GetType, menggunakan newkata kunci pengubah. Catatan, jika Anda memiliki metode umum seperti static Type Test<T>(T t) { return t.GetType(); }(tanpa batasan aktif T), hal-hal seperti itu Test<IFoo>(new BadFoo())akan tetap memanggil GetTypemetode asli .
Jeppe Stig Nielsen
2
@ Jamiec - Pertanyaan "dapatkah pola ini menimbulkan ancaman yang dapat dipercaya?" tidak retoris.
Martin Smith

Jawaban:

45

Pertanyaan bagus! Menurut saya, Anda hanya bisa menyesatkan sesama pengembang jika GetType adalah objek virtual, padahal sebenarnya bukan.

Apa yang Anda lakukan mirip dengan membayangi GetType, seperti ini:

public class BadFoo
{
    public new Type GetType()
    {
        return typeof(int);
    }
}

dengan kelas ini (dan menggunakan kode sampel dari MSDN untuk metode GetType () ) Anda memang dapat memiliki:

int n1 = 12;
BadFoo foo = new BadFoo();

Console.WriteLine("n1 and n2 are the same type: {0}",
                  Object.ReferenceEquals(n1.GetType(), foo.GetType())); 
// output: 
// n1 and n2 are the same type: True

Jadi, yikes, kamu sudah berhasil berbohong, bukan? Ya dan tidak ... Pertimbangkan bahwa menggunakan ini sebagai eksploitasi berarti menggunakan instance BadFoo Anda sebagai argumen ke metode di suatu tempat, yang mengharapkan kemungkinan sebuah objectatau tipe dasar umum untuk hierarki objek. Sesuatu seperti ini:

public void CheckIfInt(object ob)
{
    if(ob.GetType() == typeof(int))
    {
        Console.WriteLine("got an int! Initiate destruction of Universe!");
    }
    else
    {
        Console.WriteLine("not an int");
    }
}

tapi CheckIfInt(foo)mencetak "bukan int".

Jadi, pada dasarnya (kembali ke contoh Anda), Anda benar-benar hanya dapat mengeksploitasi "tipe berbohong" Anda dengan kode yang ditulis seseorang di IFooantarmuka Anda , yang sangat eksplisit tentang fakta bahwa ia memiliki metode "kustom" GetType().

Hanya jika GetType () adalah virtual pada objek Anda akan dapat membuat tipe "berbohong" yang dapat digunakan dengan metode seperti di CheckIfIntatas untuk membuat kekacauan di perpustakaan yang ditulis oleh orang lain.

Paolo Falabella
sumber
ya, itu persis sama dengan membayangi. Paragraf terakhir inilah yang benar-benar memperjelas bahwa sebenarnya tidak ada ancaman. Terima kasih!
peralihan
32

Ada dua cara untuk memastikan Type:

  1. Gunakan typeofpada Jenis yang tidak dapat dibebani

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '{0}'", typeof(BadFoo));
    Console.WriteLine("NiceFoo really is a '{0}'", typeof(NiceFoo));
    Console.ReadLine();
  2. Transmisikan instance ke objectdan panggil GetType()Metode

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '{0}'", ((object)badFoo).GetType());
    Console.WriteLine("NiceFoo really is a '{0}'", ((object)niceFoo).GetType());
    Console.ReadLine();
Johannes Wanzek
sumber
1
Bagaimana Anda akan menggunakan typeofmetode yang hanya mendapat IFoo badFooparameter sebagai?
huysentruitw
typeoftidak dapat diterapkan ke instance kelas yang perlu kita lakukan di sini. Satu-satunya pilihan Anda adalah GetType().
peralihan
Kedua sampel Anda melakukan dua hal yang berbeda. Baris kedua tidak menjawab pertanyaan yang diminta - mereka secara eksplisit "mendapatkan tipe BadFoo" tetapi tidak "mendapatkan tipe variabel badFoo."
Dan Puzey
Ya saya minta maaf. Seperti biasa saya tidak membaca pertanyaan itu dengan cukup cermat. Saya memperbarui jawaban saya untuk menunjukkan dua cara berbeda untuk memastikan jenisnya.
Johannes Wanzek
1
Inilah yang saya tunjukkan. Jadi apa gunanya komentar Anda? :)
Johannes Wanzek
10

Tidak, Anda tidak bisa membuat GetType berbohong. Anda hanya memperkenalkan metode baru. Hanya kode yang mengetahui metode ini yang akan memanggilnya.

Misalnya, Anda tidak dapat membuat kode pihak ketiga atau kerangka kerja memanggil metode GetType baru Anda alih-alih yang asli, karena kode itu tidak tahu bahwa metode Anda ada dan karena itu tidak akan pernah memanggilnya.

Namun Anda dapat membingungkan developer Anda sendiri dengan deklarasi seperti itu. Kode apa pun yang dikompilasi dengan deklarasi Anda dan yang menggunakan parameter atau variabel yang diketik sebagai IFoo atau tipe turunannya memang akan menggunakan metode baru Anda. Tapi karena itu hanya mempengaruhi kode Anda sendiri, itu tidak benar-benar menimbulkan "ancaman".

Jika Anda ingin memberikan deskripsi tipe kustom untuk kelas, ini harus dilakukan menggunakan Custom Type Descriptor , mungkin dengan memberi anotasi kelas Anda dengan TypeDescriptionProviderAttribute . Ini dapat berguna dalam beberapa situasi.

Mårten Wikström
sumber
2
+1 untuk paragraf kedua Anda secara eksplisit menunjukkan bahwa kode pihak ketiga tidak mengetahui tentang penerapan GetType khusus. Jawaban lain mengisyaratkan gagasan itu tetapi tidak benar-benar keluar dan mengatakannya (setidaknya tidak sejelas).
Brichins
7

Nah, sebenarnya ada adalah sudah jenis yang dapat berbaring di GetType: setiap jenis nullable.

Kode ini :

int? x = 0; int y = 0;
Console.WriteLine(x.GetType() == y.GetType());

keluaran True.


Sebenarnya bukan int?siapa yang bohong, hanya pemeran implisit yang objectberubah int?menjadi kotak int. Namun demikian Anda tidak bisa mengatakan int?dari intdengan GetType().

Vlad
sumber
1
Yang diharapkan (atau setidaknya perilaku terkenal). Jawaban atas pertanyaan ini menjelaskan konsep ini dengan cukup jelas, serta alasannya.
brichins
@brichins: Yah, saya setuju itu diketahui, tetapi saya tidak setuju itu baik -dikenal. Bagaimanapun, ini adalah kasus di mana GetType()menghasilkan hasil yang agak aneh. Saya sebenarnya telah bertanya kepada beberapa rekan tentang apakah non-shadowed GetType()dapat mengembalikan sesuatu yang berbeda dari tipe runtime objek sebenarnya, jawaban semua orang adalah 'tidak'.
Vlad
5

Saya tidak berpikir itu akan terjadi, karena setiap kode perpustakaan yang memanggil GetType akan mendeklarasikan variabel sebagai 'Object' atau sebagai tipe Generik 'T'

Kode berikut:

    public static void Main(string[] args)
    {
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintObjectType("BadFoo", badFoo);
        PrintObjectType("NiceFoo", niceFoo);
        PrintGenericType("BadFoo", badFoo);
        PrintGenericType("NiceFoo", niceFoo);
    }

    public static void PrintObjectType(string actualName, object instance)
    {
        Console.WriteLine("Object {0} says he's a '{1}'", actualName, instance.GetType());
    }

    public static void PrintGenericType<T>(string actualName, T instance)
    {
        Console.WriteLine("Generic Type {0} says he's a '{1}'", actualName, instance.GetType());
    }

cetakan:

Objek BadFoo mengatakan dia adalah 'TypeConcept.BadFoo'

Object NiceFoo mengatakan dia adalah 'TypeConcept.NiceFoo'

Tipe Generik BadFoo mengatakan dia adalah 'TypeConcept.BadFoo'

Tipe Generik NiceFoo mengatakan dia adalah 'TypeConcept.NiceFoo'

Satu-satunya saat kode semacam ini akan menghasilkan skenario buruk adalah di kode Anda sendiri, di mana Anda mendeklarasikan jenis parameter sebagai IFoo

    public static void Main(string[] args)
    {
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintIFoo("BadFoo", badFoo);
        PrintIFoo("NiceFoo", niceFoo);
    }

    public static void PrintIFoo(string actualName, IFoo instance)
    {
        Console.WriteLine("IFoo {0} says he's a '{1}'", actualName, instance.GetType());
    }

IFoo BadFoo mengatakan dia adalah 'System.Int32'

IFoo NiceFoo mengatakan dia adalah 'TypeConcept.NiceFoo'

Moeri
sumber
4

Hal terburuk yang bisa terjadi sejauh yang saya tahu adalah menyesatkan programmer yang tidak bersalah yang kebetulan menggunakan kelas keracunan, misalnya:

Type type = myInstance.GetType();
string fullName = type.FullName;
string output;
if (fullName.Contains(".Web"))
{
    output = "this is webby";
}
else if (fullName.Contains(".Customer"))
{
    output = "this is customer related class";
}
else
{
    output = "unknown class";
}

Jika myInstanceadalah instance dari kelas seperti yang Anda gambarkan dalam pertanyaan, itu hanya akan diperlakukan sebagai tipe yang tidak diketahui.

Jadi jawaban saya adalah tidak, tidak dapat melihat ancaman nyata di sini.

Shadow Wizard adalah Ear For You
sumber
1
Tentu. Seorang programmer yang cermat dapat melihat pada waktu kompilasi metode mana "GetType" yang dia panggil. Object.GetType()berbeda dari SomeUserdefinedInterfaceClassOrStruct.GetType(). Hanya, jika Anda menggunakan dynamictipe tersebut, Anda tidak akan pernah tahu apa yang akan terjadi pada waktu pengikatan. Jadi sebaiknya gunakan dynamic x = expression; ... Type t = ((object)x).GetType();dalam kasus seperti itu.
Jeppe Stig Nielsen
@Jeppe poin yang adil! Saya pikir itu membenarkan jawaban terpisah, jawaban saya lebih fokus pada programmer "tidak bersalah" yang tidak akan terlalu berhati-hati.
Shadow Wizard adalah Ear For You
3

Anda memiliki beberapa opsi jika Anda ingin bermain aman melawan peretasan semacam itu:

Transmisikan ke objek terlebih dahulu

Anda dapat memanggil GetType()metode asli dengan terlebih dahulu mentransmisikan instance ke object:

 Console.WriteLine("BadFoo says he's a '{0}'", ((object)badFoo).GetType());

menghasilkan:

BadFoo says he's a 'ConsoleApplication.BadFoo'

Gunakan metode template

Menggunakan metode templat ini juga akan memberi Anda tipe sebenarnya:

static Type GetType<T>(T obj)
{
    return obj.GetType();
}

GetType(badFoo);
huysentruitw
sumber
2

Ada perbedaan antara object.GetTypedan IFoo.GetType. GetTypedipanggil pada waktu kompilasi pada objek yang tidak dikenal, bukan pada Antarmuka. Dalam contoh Anda, dengan keluaran badFoo.GetTypeitu diharapkan lebih baik, karena Anda membebani metode. Hanya masalahnya, programmer lain bisa bingung dengan perilaku ini.

Tetapi jika Anda menggunakannya, typeof()itu akan menghasilkan bahwa jenisnya sama, dan Anda tidak dapat menimpa typeof().

Juga programmer dapat melihat pada waktu kompilasi, metode mana yang GetTypedia panggil.

Jadi untuk pertanyaan Anda: Pola ini tidak dapat menimbulkan ancaman yang kredibel, tetapi juga bukan gaya pengkodean terbaik.

bpoiss
sumber
badFoo.GetType()Perilaku yang diharapkan IS, karena GetTypekelebihan beban.
huysentruitw