Apakah masuk akal untuk menggunakan "sebagai" alih-alih pemain bahkan jika tidak ada cek nol? [Tutup]

351

Di blog pengembangan, contoh kode online, dan (baru-baru ini) bahkan sebuah buku, saya terus tersandung tentang kode seperti ini:

var y = x as T;
y.SomeMethod();

atau, lebih buruk lagi:

(x as T).SomeMethod();

Itu tidak masuk akal bagi saya. Jika Anda yakin bahwa xadalah tipe T, Anda harus menggunakan cor langsung: (T)x. Jika Anda tidak yakin, Anda dapat menggunakan astetapi perlu memeriksa nullsebelum melakukan beberapa operasi. Semua yang dilakukan oleh kode di atas adalah mengubah (berguna) InvalidCastExceptionmenjadi (tidak berguna) NullReferenceException.

Apakah saya satu-satunya yang berpikir bahwa ini merupakan penyalahgunaan askata kunci? Atau apakah saya melewatkan sesuatu yang jelas dan pola di atas benar-benar masuk akal?

Heinzi
sumber
56
Akan lebih lucu untuk melihat (ciuman sebagai S) .SteveIsSuchA (); Tapi saya setuju, ini penyalahgunaan.
SwDevMan81
5
Jauh lebih keren daripada menulis ((T)x).SomeMethod(), bukan? ;) (hanya bercanda, tentu saja Anda benar!)
Lucero
8
@ P Ayah saya tidak setuju, pertanyaan yang sangat bagus (apakah pola kode ini benar-benar masuk akal), dan sangat berguna. Memberi +1 pada pertanyaan dan mengerutkan kening kepada siapa saja yang memilih untuk menutup.
MarkJ
9
Lucerno benar, pola pengkodean ini diinduksi dengan mencoba menghindari tanda kurung. Tidak dapat disembuhkan setelah terkena Lisp.
Hans Passant
13
Kode yang dioptimalkan (f as T).SomeMethod()
:;

Jawaban:

252

Pemahaman Anda benar. Kedengarannya seperti mencoba mengoptimalkan mikro untuk saya. Anda harus menggunakan gips normal ketika Anda yakin akan tipenya. Selain menghasilkan pengecualian yang lebih masuk akal, itu juga gagal cepat. Jika Anda salah tentang asumsi Anda tentang jenis ini, program Anda akan segera gagal dan Anda akan dapat melihat penyebab kegagalan dengan segera daripada menunggu untuk NullReferenceExceptionatau ArgumentNullExceptionatau bahkan kesalahan logis di masa depan. Secara umum, asekspresi yang tidak diikuti oleh nullcek di suatu tempat adalah bau kode.

Di sisi lain, jika Anda tidak yakin tentang gips dan mengharapkannya gagal, Anda harus menggunakan asgips biasa yang dibungkus dengan try-catchblok. Selain itu, penggunaan asdirekomendasikan atas cek tipe diikuti oleh pemain. Dari pada:

if (x is SomeType)
   ((SomeType)x).SomeMethod();

yang menghasilkan isinstinstruksi untuk iskata kunci, dan castclassinstruksi untuk para pemain (secara efektif melakukan pemeran dua kali), Anda harus menggunakan:

var v = x as SomeType;
if (v != null)
    v.SomeMethod();

Ini hanya menghasilkan isinstinstruksi. Metode sebelumnya memiliki cacat potensial dalam aplikasi multithreaded karena kondisi balapan dapat menyebabkan variabel untuk mengubah jenisnya setelah iscek berhasil dan gagal pada garis gips. Metode terakhir tidak rentan terhadap kesalahan ini.


Solusi berikut tidak disarankan untuk digunakan dalam kode produksi. Jika Anda benar-benar membenci konstruksi mendasar seperti itu di C #, Anda dapat mempertimbangkan beralih ke VB atau bahasa lain.

Jika seseorang sangat membenci sintaksis pemeran, ia dapat menulis metode ekstensi untuk meniru pemeran:

public static T To<T>(this object o) { // Name it as you like: As, Cast, To, ...
    return (T)o;
}

dan gunakan sintaksis rapi [?]:

obj.To<SomeType>().SomeMethod()
Mehrdad Afshari
sumber
5
Saya pikir kondisi balapan tidak relevan. Jika Anda mengalami masalah ini, maka kode Anda tidak aman untuk thread, dan ada cara yang lebih andal untuk menyelesaikannya daripada menggunakan kata kunci "as". +1 untuk sisa jawabannya.
RMorrisey
10
@RMorrisey: Saya memiliki setidaknya satu contoh dalam benak: Asumsikan Anda memiliki cacheobjek yang mencoba dibatalkan thread lain dengan menyetelnya ke null. Dalam skenario bebas kunci, hal-hal semacam ini dapat muncul.
Mehrdad Afshari
10
is + cast sudah cukup untuk memicu peringatan "Jangan membuang yang tidak perlu" dari FxCop: msdn.microsoft.com/en-us/library/ms182271.aspx Itu seharusnya menjadi alasan yang cukup untuk menghindari konstruk.
David Schmitt
2
Anda harus menghindari membuat metode ekstensi Object. Penggunaan metode pada tipe nilai akan menyebabkannya menjadi kotak yang tidak perlu.
MgSam
2
@MgSam Jelas, use case seperti itu tidak masuk akal untuk Tometode di sini, karena hanya mengkonversi lintas hierarki warisan, yang untuk jenis nilai bagaimanapun juga melibatkan tinju. Tentu saja, seluruh gagasan lebih teoretis daripada serius.
Mehrdad Afshari
42

IMHO, asmasuk akal bila dikombinasikan dengan nullcek:

var y = x as T;
if (y != null)
    y.SomeMethod();
Rubens Farias
sumber
40

Menggunakan 'sebagai' tidak menerapkan konversi yang ditentukan pengguna sementara para pemain akan menggunakannya jika perlu. Itu bisa menjadi perbedaan penting dalam beberapa kasus.

Larry Fix
sumber
5
Ini penting untuk diingat. Eric Lippert membahasnya di sini: blogs.msdn.com/ericlippert/archive/2009/10/08/…
P Daddy
5
Komentar yang bagus, P! Jika kode Anda tergantung pada perbedaan ini, saya katakan ada sesi debugging larut malam di masa depan Anda.
TrueWill
36

Saya menulis sedikit tentang ini di sini:

http://blogs.msdn.com/ericlippert/archive/2009/10/08/what-s-the-difference-between-as-and-cast-operators.aspx

Saya mengerti maksud Anda. Dan saya setuju dengan dorongan itu: bahwa operator transmisi berkomunikasi "Saya yakin bahwa objek ini dapat dikonversi ke tipe itu, dan saya bersedia mengambil risiko pengecualian jika saya salah", sedangkan operator "sebagai" berkomunikasi "Saya tidak yakin objek ini dapat dikonversi ke tipe itu; beri saya nol jika saya salah".

Namun, ada perbedaan yang halus. (x sebagai T) .Apa pun yang dikomunikasikan "Saya tidak hanya tahu bahwa x dapat dikonversi ke T, tetapi lebih dari itu, melakukan itu hanya melibatkan konversi referensi atau unboxing, dan lebih jauh lagi, bahwa x bukan nol". Itu memang mengomunikasikan informasi yang berbeda dari ((T) x). Apa pun (), dan mungkin itulah yang dimaksudkan oleh pembuat kode.

Eric Lippert
sumber
1
Saya tidak setuju dengan pembelaan spekulatif Anda terhadap pembuat kode dalam kalimat terakhir Anda. ((T)x).Whatever() juga berkomunikasi yang xtidak [dimaksudkan sebagai] nol, dan saya sangat meragukan bahwa seorang penulis biasanya akan peduli apakah konversi hanya akan Tterjadi dengan konversi referensi atau unboxing, atau jika itu memerlukan konversi yang ditentukan pengguna atau yang mengubah representasi. Lagi pula, jika saya mendefinisikan public static explicit operator Foo(Bar b){}, maka jelas maksud saya yang Bardianggap kompatibel Foo. Jarang saya ingin menghindari konversi ini.
P Ayah
4
Yah, mungkin sebagian besar penulis kode tidak akan membuat perbedaan yang halus. Saya pribadi mungkin, tetapi jika ya, saya akan menambahkan komentar untuk efek itu.
Eric Lippert
16

Saya sering melihat referensi ke artikel yang menyesatkan ini sebagai bukti bahwa "sebagai" lebih cepat daripada casting.

Salah satu aspek menyesatkan yang lebih jelas dari artikel ini adalah grafik, yang tidak menunjukkan apa yang sedang diukur: Saya menduga itu mengukur gips gagal (di mana "as" jelas jauh lebih cepat karena tidak ada pengecualian dilemparkan).

Jika Anda meluangkan waktu untuk melakukan pengukuran, maka Anda akan melihat bahwa casting, seperti yang Anda harapkan, lebih cepat dari "sebagai" ketika para pemain berhasil.

Saya menduga ini mungkin menjadi salah satu alasan penggunaan "pemujaan kargo" sebagai kata kunci alih-alih pemain.

Joe
sumber
2
Terima kasih atas tautannya, itu sangat menarik. Dari bagaimana saya memahami artikel itu, ia tidak membandingkan kasus non-pengecualian. Namun demikian, artikel itu ditulis untuk .net 1.1, dan komentar menunjukkan bahwa ini berubah di .net 2.0: Kinerja sekarang hampir sama, dengan para pemain awalan bahkan menjadi sedikit lebih cepat.
Heinzi
1
Artikel ini tidak menyiratkan bahwa dia membandingkan kasus non-pengecualian, tetapi saya melakukan beberapa tes sejak lama dan tidak dapat mereproduksi hasil yang diklaimnya, bahkan dengan .NET 1.x. Dan karena artikel itu tidak menyediakan kode yang digunakan untuk menjalankan benchmark, tidak mungkin untuk mengatakan apa yang dibandingkan.
Joe
"Kultus kargo" - sempurna. Lihat "Cargo Cult Science Richard Feynman" untuk info selengkapnya.
Bob Denny
11

Para pemeran langsung membutuhkan sepasang tanda kurung lebih dari askata kunci. Jadi, bahkan dalam kasus di mana Anda 100% yakin apa jenisnya, itu mengurangi kekacauan visual.

Menyetujui hal pengecualian, meskipun. Tapi setidaknya bagi saya, sebagian besar menggunakan asboil down untuk memeriksa nullsetelahnya, yang menurut saya lebih bagus daripada menangkap pengecualian.

Joey
sumber
8

99% waktu saya menggunakan "as" adalah ketika saya tidak yakin apa tipe objek yang sebenarnya

var x = obj as T;
if(x != null){
 //x was type T!
}

dan saya tidak ingin menangkap pengecualian pemain eksplisit atau membuat pemain dua kali, menggunakan "is":

//I don't like this
if(obj is T){
  var x = (T)obj; 
}
Max Galkin
sumber
8
Anda baru saja menggambarkan kasus penggunaan yang tepat untuk as. Apa yang 1% lainnya?
P Ayah
Dalam salah ketik? =) Maksud saya 99% dari waktu saya menggunakan cuplikan kode yang tepat ini , sementara kadang-kadang saya dapat menggunakan "sebagai" dalam pemanggilan metode atau tempat lain.
Max Galkin
Doh, dan bagaimana itu kurang berguna daripada jawaban populer kedua ???
Max Galkin
2
+1 Saya setuju menyebut ini sama berharganya dengan jawaban Rubens Farias - semoga orang-orang datang ke sini dan ini akan menjadi contoh yang bermanfaat
Ruben Bartelink
8

Hanya karena orang menyukai tampilannya, ini sangat mudah dibaca.

Mari kita hadapi itu: operator casting / konversi dalam bahasa seperti-C cukup mengerikan, mudah dibaca. Saya ingin lebih baik jika C # mengadopsi sintaks Javascript:

object o = 1;
int i = int(o);

Atau tentukan tooperator, setara dengan casting as:

object o = 1;
int i = o to int;
JulianR
sumber
Asal tahu saja, sintaksis JavaScript yang Anda sebutkan juga diizinkan di C ++.
P Ayah
@PDaddy: Meskipun ini bukan sintaks alternatif langsung yang kompatibel 100% dan tidak dimaksudkan sebagai itu (operator X vs konstruktor konversi)
Ruben Bartelink
Saya lebih suka menggunakan sintaks C ++ dari dynamic_cast<>()(dan serupa). Anda melakukan sesuatu yang jelek, itu harus terlihat jelek.
Tom Hawtin - tackline
5

Orang-orang assangat menyukainya karena itu membuat mereka merasa aman dari pengecualian ... Seperti jaminan pada sebuah kotak. Seorang pria memberikan jaminan mewah pada kotak itu karena dia ingin Anda merasa hangat dan hangat di dalam kotak. Anda pikir Anda meletakkan kotak kecil itu di bawah bantal Anda di malam hari, Peri Jaminan mungkin turun dan meninggalkan seperempat, apakah saya benar Ted?

Kembali ke topik ... saat menggunakan pemain langsung, ada kemungkinan pengecualian pemain tidak valid. Jadi orang berlaku assebagai solusi selimut untuk semua kebutuhan casting mereka karena as(dengan sendirinya) tidak akan pernah membuang pengecualian. Tapi lucunya tentang itu, ada dalam contoh yang Anda berikan(x as T).SomeMethod(); Anda perdagangan pengecualian pemain tidak valid untuk pengecualian referensi nol. Yang mengaburkan masalah sebenarnya ketika Anda melihat pengecualian.

Saya biasanya tidak menggunakan asterlalu banyak. Saya lebih suka istes karena bagi saya, tampaknya lebih mudah dibaca dan lebih masuk akal daripada mencoba pemain dan memeriksa nol.

Bob
sumber
2
"Saya lebih suka tes is" - "is" diikuti oleh pemain tentu saja lebih lambat daripada "sebagai" diikuti oleh tes untuk null (seperti "IDictionary.ContainsKey" diikuti oleh dereferencing menggunakan pengindeks lebih lambat daripada "IDictionary.TryGetValue "). Tetapi jika Anda merasa lebih mudah dibaca, tidak diragukan perbedaannya jarang signifikan.
Joe
Pernyataan penting di bagian tengah adalah bagaimana orang berlaku assebagai solusi selimut karena itu membuat mereka merasa aman.
Bob
5

Ini pasti salah satu yang paling kencing di antara saya .

D&E Stroustrup dan / atau beberapa posting blog yang tidak dapat saya temukan saat ini membahas gagasan tentang tooperator yang akan membahas poin yang dibuat oleh https://stackoverflow.com/users/73070/johannes-rossel (yaitu, sintaksis yang sama astetapi denganDirectCast semantik) ).

Alasan mengapa ini tidak diterapkan adalah karena pemain harus menyebabkan rasa sakit dan menjadi jelek sehingga Anda terdorong untuk tidak menggunakannya.

Kasihan bahwa programmer 'pintar' (sering penulis buku (Juval Lowy IIRC)) melangkah sekitar ini dengan menyalahgunakan asdengan cara ini (C ++ tidak menawarkan as, mungkin karena alasan ini).

Bahkan VB memiliki konsistensi lebih dalam memiliki sintaks seragam yang memaksa Anda untuk memilih TryCastatau DirectCastdan membuat pikiran Anda !

Ruben Bartelink
sumber
+1. Anda mungkin berarti DirectCast perilaku , bukan sintaksis .
Heinzi
@Heinzi: Ta untuk +1. Poin yang bagus. Memutuskan untuk menjadi orang yang pandai dan menggunakan semantics: P
Ruben Bartelink
Mengingat bahwa C # tidak berpura-pura kompatibilitas dengan C, C ++, atau Java, saya mendapati diri saya kesal pada beberapa hal yang dipinjam dari bahasa-bahasa tersebut. Ini melampaui "Saya tahu ini adalah X", dan "Saya tahu ini bukan X, tetapi dapat direpresentasikan sebagai satu", hingga "Saya tahu ini bukan X, dan mungkin tidak benar-benar dapat direpresentasikan sebagai satu , tapi tetap beri saya X. " Saya bisa melihat kegunaan untuk double-to-intpemain yang akan gagal jika doubletidak mewakili nilai tepat yang bisa masuk Int32, tetapi memiliki (int)-1.5hasil -1 hanyalah jelek.
supercat
@supercat Yap, tapi desain bahasa tidak semudah seperti yang kita semua tahu - lihat set pengorbanan yang terlibat dalam C # nullables. Satu-satunya penangkal yang diketahui adalah pembacaan reguler C # dalam edisi Kedalaman saat edisi tersebut muncul :) Syukurlah saya lebih peduli dengan memahami nuansa F # hari ini dan jauh lebih waras di sekitar banyak masalah ini.
Ruben Bartelink
@ RubenBartelink: Saya tidak begitu jelas masalah apa yang seharusnya dipecahkan oleh tipe nullable, tapi saya pikir dalam banyak kasus akan lebih baik untuk memiliki MaybeValid<T>dua bidang publik IsValiddan Valuekode mana yang bisa dilakukan sesuai dengan keinginan. Itu akan memungkinkan misalnya MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }. Tidak hanya akan menghemat setidaknya dua operasi penyalinan dibandingkan dengan Nullable<T>, tetapi juga bisa bernilai dengan jenis apa pun -T bukan hanya kelas.
supercat
2

Saya percaya bahwa askata kunci dapat dianggap sebagai versi yang lebih elegan dynamic_castdari C ++.

Andrew Garrison
sumber
Saya akan mengatakan gips lurus dalam C # lebih seperti dynamic_castdi C ++.
P Ayah
saya pikir gips lurus di C # lebih setara dengan static_cast di C ++.
Andrew Garrison
2
@Ruben Bartelink: Ini hanya mengembalikan null dengan pointer. Dengan referensi, yang harus Anda gunakan bila memungkinkan, ia melempar std::bad_cast.
P Ayah
1
@Andrew Garrison: static_casttidak melakukan pemeriksaan jenis runtime. Tidak ada pemeran yang serupa dengan ini di C #.
P Ayah
Sayangnya, saya tidak tahu Anda bahkan bisa menggunakan gips pada referensi, karena saya hanya pernah menggunakannya pada pointer, tetapi P Daddy benar-benar benar!
Andrew Garrison
1

Mungkin lebih populer tanpa alasan teknis tetapi hanya karena lebih mudah dibaca dan lebih intuitif. (Tidak mengatakan itu membuatnya lebih baik hanya mencoba menjawab pertanyaan)

Jla
sumber
1

Salah satu alasan untuk menggunakan "sebagai":

T t = obj as T;
 //some other thread changes obj to another type...
if (t != null) action(t); //still works

Alih-alih (kode buruk):

if (obj is T)
{
     //bang, some other thread changes obj to another type...
     action((T)obj); //InvalidCastException
}
Rauhotz
sumber
2
Jika Anda memiliki kondisi balapan ini, Anda memiliki masalah yang lebih besar (tapi setujui sampel yang bagus untuk digunakan bersama yang lain, jadi +1
Ruben Bartelink
-1 karena ini melanggengkan kekeliruan. Jika utas lain dapat mengubah jenis objek, maka Anda masih memiliki masalah. Klaim "// masih berfungsi" sangat tidak sah untuk dipercaya, karena t akan digunakan sebagai penunjuk ke T, tetapi menunjuk ke memori yang bukan lagi solusi T. Tidak ada solusi yang akan berfungsi saat utas lainnya mengubah jenis obj saat aksi (t) sedang berlangsung.
Stephen C. Steel
5
@Stephen C. Steel: Sepertinya Anda agak bingung. Mengubah jenis objberarti mengubah objvariabel itu sendiri untuk menyimpan referensi ke objek lain. Itu tidak akan mengubah isi memori di mana berada objek awalnya dirujuk oleh obj. Objek asli ini akan tetap tidak berubah, dan tvariabel masih akan menyimpan referensi untuk itu.
P Ayah
1
@ P Ayah - Saya pikir Anda benar, dan saya salah: jika obj rebound dari objek T ke objek T2, maka t masih akan menunjuk ke objek T yang lama. Karena t masih mereferensikan objek yang lama, itu tidak bisa menjadi sampah yang dikumpulkan, sehingga objek T yang lama akan tetap valid. Rangkaian detektor kondisi ras saya dilatih pada C ++, di mana kode yang sama menggunakan dynamic_cast akan menjadi masalah potensial.
Stephen C. Steel