'is' เทียบกับลองแคสต์ด้วยการตรวจสอบ null


108

ฉันสังเกตว่า Resharper แนะนำให้ฉันเปลี่ยนสิ่งนี้:

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

ในสิ่งนี้:

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

เหตุใดจึงแนะนำการเปลี่ยนแปลงนี้ ฉันคุ้นเคยกับ Resharper ที่แนะนำการเปลี่ยนแปลงการเพิ่มประสิทธิภาพและการเปลี่ยนแปลงการลดรหัส แต่สิ่งนี้รู้สึกว่าต้องการใช้คำสั่งเดียวของฉันและเปลี่ยนเป็นสองซับ

ตามMSDN :

คือ การแสดงออกประเมินจริงถ้าทั้งสองเงื่อนไขต่อไปนี้:

นิพจน์ไม่เป็นโมฆะ สามารถส่งนิพจน์ไปพิมพ์ได้ นั่นคือการแสดงออกของแบบฟอร์ม(type)(expression)จะสมบูรณ์โดยไม่มีข้อยกเว้น

ฉันอ่านผิดหรือไม่ได้isทำการตรวจสอบแบบเดียวกันทุกประการเพียงแค่บรรทัดเดียวโดยไม่จำเป็นต้องสร้างตัวแปรท้องถิ่นอื่นอย่างชัดเจนสำหรับการตรวจสอบ null


1
คุณใช้ myObjRef ในภายหลังในโค้ดหรือไม่ ถ้าคุณเป็นคุณจะไม่ต้องการผู้เริ่มต้นMyPropหลังจากการเปลี่ยนแปลงนี้
ค่าเริ่มต้น

คำตอบ:


149

เพราะมีนักแสดงเพียงคนเดียว เปรียบเทียบสิ่งนี้:

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
    ...
}

สำหรับสิ่งนี้:

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 รองรับไวยากรณ์ที่กะทัดรัดยิ่งขึ้นโดยใช้การจับคู่รูปแบบ :

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

3
เป๊ะ การใช้ 'is' โดยทั่วไปจะทำบางสิ่งเช่น return ((myProp as MyType) == null)
Bambu

2
แม้ว่าการเปลี่ยนแปลงจะดำเนินไป แต่นี่ก็เป็นนาทีที่ดี การตรวจสอบค่าว่างจะค่อนข้างเทียบได้กับการตรวจสอบประเภทที่สอง asอาจเร็วกว่าสองสามนาโนวินาที แต่ฉันคิดว่านี่เป็นการเพิ่มประสิทธิภาพขนาดเล็กก่อนกำหนด
Servy

4
โปรดทราบว่าเวอร์ชันดั้งเดิมไม่ปลอดภัยต่อเธรด ค่าของmyObjหรือmyPropอาจเปลี่ยนแปลงได้ (โดยเธรดอื่น) ระหว่างisการร่ายและทำให้เกิดพฤติกรรมที่ไม่พึงปรารถนา
Jeff E

1
ฉันอาจเพิ่มว่าการใช้as+ != nullจะเรียกใช้ตัวดำเนินการที่ถูกแทนที่!=ด้วยMyTypeif ที่กำหนดไว้ (แม้ว่าmyObjRefจะเป็นโมฆะ) แม้ว่าในกรณีส่วนใหญ่จะไม่ใช่ปัญหา (โดยเฉพาะอย่างยิ่งหากคุณนำไปใช้อย่างถูกต้อง) แต่ในบางกรณีอาจไม่เป็นที่ต้องการ (โค้ดที่ไม่ดีประสิทธิภาพ) (คงจะต้องค่อนข้างสุดโต่ง )
Chris Sinclair

1
@ Chris: object.ReferenceEquals(null, myObjRef)ใช่การแปลที่ถูกต้องของรหัสที่จะใช้
Ben Voigt

10

ตัวเลือกที่ดีที่สุดคือใช้การจับคู่รูปแบบเช่นนั้น:

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

อันนี้ดีกว่าส่วนที่สองจากคำถามอย่างไร?
Victor Yarema

ส่วนที่สองของคำถามหมายถึงการใช้งานพื้นฐานของ is (โดยไม่มีการประกาศตัวแปร) และในกรณีนี้คุณจะตรวจสอบประเภทสองครั้ง (หนึ่งในคำสั่ง is และอีกชิ้นหนึ่งก่อนการแสดง)
Francesco Cattoni

6

ยังไม่มีข้อมูลเกี่ยวกับสิ่งที่เกิดขึ้นจริงใต้เข็มขัด ดูตัวอย่างนี้:

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

สิ่งนี้แปลเป็น IL ต่อไปนี้:

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   

สิ่งสำคัญที่นี่คือisinstและการcastclassโทร - ทั้งที่ค่อนข้างแพง หากคุณเปรียบเทียบสิ่งนั้นกับทางเลือกอื่นคุณจะเห็นว่ามีเพียงการisinstตรวจสอบ:

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  

สิ่งที่ควรค่าแก่การกล่าวถึงคือประเภทค่าจะใช้unbox.anyมากกว่า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   

แต่ทราบว่าเรื่องนี้ไม่จำเป็นต้องแปลว่าจะได้เร็วขึ้นส่งผลให้ในขณะที่เราสามารถดูได้ที่นี่ ดูเหมือนจะมีการปรับปรุงตั้งแต่คำถามที่ถูกถามว่า: บรรยากาศดูเหมือนจะดำเนินการให้เร็วที่สุดเท่าที่พวกเขาเคยเป็น แต่asและlinqในขณะนี้มีประมาณ 3 ครั้งได้เร็วขึ้น


4

คำเตือน Resharper:

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

ทั้งสองอย่างจะได้ผลขึ้นอยู่กับว่าโค้ดของคุณเหมาะกับคุณมากขึ้นอย่างไร ในกรณีของฉันฉันไม่สนใจคำเตือนนั้น:

//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();

ในรหัสของฉันวิธีที่ 2 นั้นยาวกว่าและประสิทธิภาพแย่ลง


1
ในตัวอย่างโลกแห่งความเป็นจริงมีเพียงปัญหาในการออกแบบ หากคุณควบคุมประเภทเพียงใช้อินเทอร์เฟซเช่นIRunable. หากคุณไม่ได้รับการควบคุมบางทีคุณอาจใช้dynamic?
M. Mimpen

3

สำหรับฉันแล้วสิ่งนี้ดูเหมือนจะขึ้นอยู่กับอัตราต่อรองว่าจะเป็นประเภทนั้นหรือไม่ แน่นอนว่าจะมีประสิทธิภาพมากขึ้นในการโยนขึ้นด้านหน้าหากวัตถุเป็นประเภทนั้นเกือบตลอดเวลา หากเป็นเพียงบางครั้งในประเภทนั้นอาจเป็นการดีที่สุดที่จะตรวจสอบก่อนด้วยคือ

ค่าใช้จ่ายในการสร้างตัวแปรท้องถิ่นนั้นน้อยมากเมื่อเทียบกับค่าใช้จ่ายของการตรวจสอบประเภท

ความสามารถในการอ่านและขอบเขตเป็นปัจจัยสำคัญสำหรับฉันโดยทั่วไป ฉันไม่เห็นด้วยกับ ReSharper และใช้ตัวดำเนินการ "is" ด้วยเหตุผลนั้นเพียงอย่างเดียว เพิ่มประสิทธิภาพในภายหลังหากนี่เป็นคอขวดจริง

(ฉันสมมติว่าคุณใช้เพียงmyObj.myProp is MyTypeครั้งเดียวในฟังก์ชันนี้)


0

ควรแนะนำการเปลี่ยนแปลงครั้งที่สองเช่นกัน:

(MyType)myObj.myProp

เป็น

myObjRef

สิ่งนี้ช่วยประหยัดการเข้าถึงคุณสมบัติและการแคสต์เมื่อเทียบกับรหัสเดิม แต่มันเป็นไปได้เฉพาะหลังจากที่เปลี่ยนแปลงไปisas


@Default: ไม่มันไม่ใช่ ไม่ได้หมายความว่าไม่มีอยู่ในโค้ด
Ben Voigt

1
ขอโทษ.. เข้าใจผิด. อย่างไรก็ตาม(MyType)จะทิ้งข้อยกเว้นหากการร่ายล้มเหลว ผลตอบแทนเท่านั้นas null
ค่าเริ่มต้น

@ ค่าเริ่มต้น: การแคสต์จะไม่ล้มเหลวเนื่องจากมีการตรวจสอบประเภทแล้วis(รหัสนั้นอยู่ในคำถาม)
Ben Voigt

1
อย่างไรก็ตาม re # ต้องการแทนที่รหัสนั้น - หมายความว่าจะไม่มีอยู่หลังจากการเปลี่ยนแปลงที่แนะนำ
ค่าเริ่มต้น

ฉันคิดว่าฉันกำลังติดตามความคิดของคุณที่นี่ (ใช้เวลาพอสมควร) คุณหมายความว่าบรรทัดแรกอยู่ที่ไหนสักแห่งในโค้ดและบรรทัดนั้นจะถูกทำให้ง่ายขึ้นหลังจากคำแนะนำ Re # ไปยังบรรทัดที่สอง?
ค่าเริ่มต้น

0

ฉันจะบอกว่านี่คือการสร้าง myObj.myProp เวอร์ชันที่พิมพ์ยากซึ่งก็คือ myObjRef จากนั้นควรใช้เมื่อคุณอ้างถึงค่านี้ในบล็อกเทียบกับต้องทำการแคสต์

ตัวอย่างเช่นสิ่งนี้:

myObjRef.SomeProperty

ดีกว่านี้:

((MyType)myObj.myProp).SomeProperty
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.