'is' versus try cast dengan pemeriksaan nol

107

Saya perhatikan bahwa Resharper menyarankan agar saya mengubah ini:

if (myObj.myProp is MyType)
{
   ...
}

ke dalam ini:

var myObjRef = myObj.myProp as MyType;
if (myObjRef != null)
{
   ...
}

Mengapa ini menyarankan perubahan ini? Saya terbiasa dengan Resharper yang menyarankan perubahan pengoptimalan dan perubahan pengurangan kode, tetapi ini terasa seperti ingin mengambil pernyataan tunggal saya dan mengubahnya menjadi dua baris.

Menurut MSDN :

Sebuah adalah ekspresi mengevaluasi true jika kedua kondisi berikut terpenuhi:

ekspresi tidak nol. ekspresi dapat dilemparkan untuk diketik . Artinya, ekspresi cast formulir (type)(expression)akan selesai tanpa mengeluarkan pengecualian.

Apakah saya salah membaca itu, atau tidak ismelakukan pemeriksaan yang sama persis, hanya dalam satu baris tanpa perlu secara eksplisit membuat variabel lokal lain untuk pemeriksaan null?

HotN
sumber
1
apakah Anda menggunakan myObjRef nanti dalam kode? jika ya, Anda tidak akan membutuhkan MyProppengambil setelah perubahan ini.
Default

Jawaban:

147

Karena hanya ada satu pemeran. Bandingkan ini:

if (myObj.myProp is MyType) // cast #1
{
    var myObjRef = (MyType)myObj.myProp; // needs to be cast a second time
                                         // before using it as a MyType
    ...
}

untuk ini:

var myObjRef = myObj.myProp as MyType; // only one cast
if (myObjRef != null)
{
    // myObjRef is already MyType and doesn't need to be cast again
    ...
}

C # 7.0 mendukung sintaks yang lebih ringkas menggunakan pencocokan pola :

if (myObj.myProp is MyType myObjRef)
{
    ...
}
Jeff E
sumber
3
persis. menggunakan 'adalah' pada dasarnya melakukan sesuatu seperti return ((myProp as MyType) == null)
Bambu
2
Sejauh perubahan terjadi, ini cukup menit. Pemeriksaan nol akan cukup sebanding dengan pemeriksaan tipe kedua. asmungkin beberapa nanodetik lebih cepat, tapi saya menganggap ini sebagai pengoptimalan mikro prematur.
Pelayanan
4
Perhatikan juga bahwa versi asli tidak aman untuk thread. Nilai myObjatau myPropbisa berubah (oleh utas lain) antara isdan pemeran, menyebabkan perilaku yang tidak diinginkan.
Jeff E
1
Saya juga dapat menambahkan bahwa menggunakan as+ != nulljuga akan mengeksekusi !=operator yang diganti MyTypejika ditentukan (bahkan jika myObjRefnol). Meskipun dalam banyak kasus ini bukan masalah (terutama jika Anda menerapkannya dengan benar), dalam beberapa kasus ekstrim (kode buruk, kinerja) mungkin tidak diinginkan. ( meskipun harus sangat ekstrim )
Chris Sinclair
1
@ Chris: Benar, terjemahan kode yang benar akan digunakan object.ReferenceEquals(null, myObjRef).
Ben Voigt
10

Pilihan terbaik adalah menggunakan pencocokan pola seperti itu:

if (value is MyType casted){
    //Code with casted as MyType
    //value is still the same
}
//Note: casted can be used outside (after) the 'if' scope, too
Francesco Cattoni
sumber
Bagaimana tepatnya yang satu ini lebih baik daripada penggalan kedua dari pertanyaan?
Victor Yarema
Fragmen kedua dari pertanyaan mengacu pada penggunaan dasar is (tanpa deklarasi variabel) dan dalam hal ini Anda akan memeriksa jenisnya dua kali (satu di pernyataan is dan satu lagi sebelum pemeran)
Francesco Cattoni
6

Belum ada informasi tentang apa yang sebenarnya terjadi di bawah ikat pinggang. Lihat contoh ini:

object o = "test";
if (o is string)
{
    var x = (string) o;
}

Ini diterjemahkan menjadi IL berikut:

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  ldnull      
IL_000E:  cgt.un      
IL_0010:  stloc.1     
IL_0011:  ldloc.1     
IL_0012:  brfalse.s   IL_001D
IL_0014:  nop         
IL_0015:  ldloc.0     // o
IL_0016:  castclass   System.String
IL_001B:  stloc.2     // x
IL_001C:  nop         
IL_001D:  ret   

Yang penting di sini adalah panggilan isinstdan castclass- keduanya relatif mahal. Jika Anda membandingkannya dengan alternatif, Anda hanya dapat melihatnya melakukan isinstpemeriksaan:

object o = "test";
var oAsString = o as string;
if (oAsString != null)
{

}

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  stloc.1     // oAsString
IL_000E:  ldloc.1     // oAsString
IL_000F:  ldnull      
IL_0010:  cgt.un      
IL_0012:  stloc.2     
IL_0013:  ldloc.2     
IL_0014:  brfalse.s   IL_0018
IL_0016:  nop         
IL_0017:  nop         
IL_0018:  ret  

Juga perlu disebutkan adalah bahwa tipe nilai akan digunakan unbox.anydaripada castclass:

object o = 5;
if (o is int)
{
    var x = (int)o;
}

IL_0000:  nop         
IL_0001:  ldc.i4.5    
IL_0002:  box         System.Int32
IL_0007:  stloc.0     // o
IL_0008:  ldloc.0     // o
IL_0009:  isinst      System.Int32
IL_000E:  ldnull      
IL_000F:  cgt.un      
IL_0011:  stloc.1     
IL_0012:  ldloc.1     
IL_0013:  brfalse.s   IL_001E
IL_0015:  nop         
IL_0016:  ldloc.0     // o
IL_0017:  unbox.any   System.Int32
IL_001C:  stloc.2     // x
IL_001D:  nop         
IL_001E:  ret   

Namun perlu dicatat bahwa ini tidak selalu berarti hasil yang lebih cepat seperti yang kita lihat di sini . Ada tampaknya telah perbaikan sejak pertanyaan yang diminta meskipun: gips tampaknya dilakukan secepat mereka dulu tapi asdan linqsekarang sekitar 3 kali lebih cepat.

Jeroen Vannevel
sumber
4

Peringatan Resharper:

"Type check and direct cast can be replaced with try cast and check for null"

Keduanya akan berfungsi, itu tergantung bagaimana kode Anda lebih cocok untuk Anda. Dalam kasus saya, saya mengabaikan peringatan itu:

//1st way is n+1 times of casting
if (x is A) ((A)x).Run();
else if (x is B) ((B)x).Run();
else if (x is C) ((C)x).Run();
else if (x is D) ((D)x).Run();
//...
else if (x is N) ((N)x).Run();    
//...
else if (x is Z) ((Z)x).Run();

//2nd way is z times of casting
var a = x as Type A;
var b = x as Type B;
var c = x as Type C;
//..
var n = x as Type N;
//..
var z = x as Type Z;
if (a != null) a.Run();
elseif (b != null) b.Run();
elseif (c != null) c.Run();
...
elseif (n != null) n.Run();
...
elseif (x != null) x.Run();

Dalam kode saya cara ke-2 lebih lama dan kinerja yang lebih buruk.

Tom
sumber
1
Dalam contoh dunia nyata Anda, hanya ada masalah desain. Jika Anda mengontrol jenisnya, cukup gunakan antarmuka seperti IRunable. Jika Anda tidak memiliki kendali, mungkin Anda bisa menggunakan dynamic?
M. Mimpen
3

Bagi saya ini tampaknya tergantung pada kemungkinan besar itu akan menjadi jenis itu atau tidak. Ini tentu akan lebih efisien untuk melakukan pengecoran di depan jika objek tersebut sebagian besar waktu. Jika hanya sesekali dari jenis itu maka mungkin lebih optimal untuk diperiksa dulu dengan is.

Biaya pembuatan variabel lokal sangat kecil dibandingkan dengan biaya pemeriksaan tipe.

Keterbacaan dan cakupan adalah faktor yang lebih penting bagi saya biasanya. Saya tidak setuju dengan ReSharper, dan menggunakan operator "adalah" untuk alasan itu saja; optimalkan nanti jika ini benar-benar hambatan.

(Saya berasumsi bahwa Anda hanya menggunakan myObj.myProp is MyTypesekali dalam fungsi ini)

Kerekan
sumber
0

Ini harus menyarankan perubahan kedua juga:

(MyType)myObj.myProp

ke

myObjRef

Ini menghemat akses properti dan pemeran, dibandingkan dengan kode asli. Tapi itu hanya mungkin setelah berubah ismenjadi as.

Ben Voigt
sumber
@Default: Tidak, bukan. Itu tidak berarti itu tidak ada dalam kode.
Ben Voigt
1
maaf .. salah paham. namun, (MyType)akan memunculkan pengecualian jika cast gagal. ashanya kembali null.
Default
@Default: Pemeran tidak akan gagal, karena jenisnya telah diperiksa dengan is(kode itu ada dalam pertanyaan).
Ben Voigt
1
Namun, re # ingin mengganti kode itu - artinya kode itu tidak akan ada setelah perubahan yang disarankan.
Default
Saya pikir saya mengikuti pemikiran Anda di sini (hanya butuh waktu). Maksud Anda baris pertama ada di suatu tempat dalam kode dan baris itu akan disederhanakan setelah saran Re # ke baris kedua?
Default
0

Saya akan mengatakan ini untuk membuat versi myObj.myProp yang sangat diketik, yaitu myObjRef. Ini kemudian harus digunakan saat Anda mereferensikan nilai ini di blok, vs. harus melakukan cast.

Misalnya, ini:

myObjRef.SomeProperty

lebih baik dari ini:

((MyType)myObj.myProp).SomeProperty
Jerad Rose
sumber