'là' so với thử truyền với kiểm tra null


106

Tôi nhận thấy rằng Resharper gợi ý rằng tôi nên chuyển điều này:

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

vào cái này:

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

Tại sao nó sẽ đề xuất sự thay đổi này? Tôi đã quen với việc Resharper đề xuất các thay đổi tối ưu hóa và thay đổi giảm mã, nhưng điều này có vẻ như nó muốn lấy một câu lệnh duy nhất của tôi và biến nó thành một dấu hai dòng.

Theo MSDN :

Biểu thức An được đánh giá là true nếu cả hai điều kiện sau được đáp ứng:

biểu thức không rỗng. biểu thức có thể được ép kiểu . Nghĩa là, một biểu thức ép kiểu của biểu mẫu (type)(expression)sẽ hoàn thành mà không có ngoại lệ.

Tôi có đang đọc sai điều đó hay không isthực hiện các kiểm tra giống hệt nhau, chỉ trong một dòng đơn lẻ mà không cần phải tạo rõ ràng một biến cục bộ khác cho kiểm tra rỗng?


1
bạn có đang sử dụng myObjRef sau này trong mã không? nếu đúng như vậy, bạn sẽ không cần đến MyPropsự thay đổi sau khi thay đổi này.
Mặc định

Câu trả lời:


145

Bởi vì chỉ có một dàn diễn viên. So sánh cái này:

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

đến điều này:

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 hỗ trợ cú pháp nhỏ gọn hơn bằng cách sử dụng đối sánh mẫu :

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

3
chính xác. sử dụng 'được' về cơ bản là làm một cái gì đó giống như sự trở lại ((myProp như MyType) == null)
Bambu

2
Theo như những thay đổi xảy ra, thì đây là một phút tốt. Kiểm tra null sẽ khá so sánh với kiểm tra loại thứ hai. ascó thể nhanh hơn vài nano giây, nhưng tôi coi đây là một quá trình vi mô hóa quá sớm.
Servy

4
Cũng lưu ý rằng phiên bản gốc không an toàn theo luồng. Giá trị của myObjhoặc myPropcó thể bị thay đổi (bởi một chuỗi khác) giữa isvà ép kiểu, gây ra hành vi không mong muốn.
Jeff E

1
Tôi cũng có thể thêm rằng việc sử dụng as+ != nullcũng sẽ thực thi !=toán tử ghi đè của MyTypeif được định nghĩa (ngay cả khi myObjReflà null). Mặc dù trong hầu hết các trường hợp, đây không phải là vấn đề (đặc biệt nếu bạn triển khai đúng cách), nhưng trong một số trường hợp cực đoan (mã xấu, hiệu suất) thì điều đó có thể không được mong muốn. (sẽ phải khá cực đoan mặc dù)
Chris Sinclair

1
@Chris: Đúng vậy, bản dịch mã chính xác sẽ sử dụng object.ReferenceEquals(null, myObjRef).
Ben Voigt

10

Tùy chọn tốt nhất là sử dụng đối sánh mẫu như vậy:

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

Chính xác thì đoạn này tốt hơn đoạn thứ hai từ câu hỏi như thế nào?
Victor Yarema

Phần thứ hai của câu hỏi đề cập đến cách sử dụng cơ bản của is (không có khai báo biến) và trong trường hợp đó, bạn sẽ kiểm tra kiểu hai lần (một trong câu lệnh is và một trước khi
ép kiểu

6

Vẫn chưa có thông tin về những gì thực sự xảy ra bên dưới vành đai. Hãy xem ví dụ này:

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

Điều này chuyển thành IL sau:

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   

Điều quan trọng ở đây là cuộc gọi isinstcastclasscuộc gọi - cả hai đều tương đối đắt tiền. Nếu bạn so sánh nó với giải pháp thay thế, bạn có thể thấy nó chỉ isinstkiểm tra:

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  

Cũng đáng nói là một kiểu giá trị sẽ sử dụng unbox.anythay vì 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   

Tuy nhiên, lưu ý rằng điều này không nhất thiết phải chuyển thành kết quả nhanh hơn như chúng ta có thể thấy ở đây . Mặc dù vậy, dường như đã có những cải tiến kể từ khi câu hỏi đó được đặt ra: phôi dường như được thực hiện nhanh như trước đây nhưng aslinqhiện nhanh hơn khoảng 3 lần.


4

Cảnh báo về trình sạc lại:

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

Cả hai đều sẽ hoạt động, nó phụ thuộc vào cách mã của bạn phù hợp với bạn hơn. Trong trường hợp của tôi, tôi chỉ bỏ qua cảnh báo đó:

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

Theo cách thứ 2, mã của tôi dài hơn và hiệu suất kém hơn.


1
Trong ví dụ thực tế của bạn, chỉ đơn giản là có một vấn đề thiết kế. Nếu bạn kiểm soát các loại, chỉ cần sử dụng một giao diện chẳng hạn như IRunable. Nếu bạn không có quyền kiểm soát, có lẽ bạn có thể sử dụng dynamic?
M. Mimpen

3

Đối với tôi, điều này có vẻ phụ thuộc vào khả năng xảy ra là nó có thuộc loại đó hay không. Chắc chắn sẽ hiệu quả hơn nếu thực hiện ép kiểu trước nếu đối tượng thuộc loại đó hầu hết thời gian. Nếu nó chỉ thỉnh thoảng thuộc loại đó thì có thể kiểm tra trước là tối ưu hơn.

Chi phí tạo ra một biến cục bộ rất không đáng kể so với chi phí của việc kiểm tra kiểu.

Tính dễ đọc và phạm vi là những yếu tố quan trọng hơn đối với tôi. Tôi không đồng ý với ReSharper và chỉ sử dụng toán tử "is" vì lý do đó; tối ưu hóa sau nếu đây là một nút cổ chai thực sự.

(Tôi giả định rằng bạn chỉ sử dụng myObj.myProp is MyTypemột lần trong chức năng này)


0

Nó cũng nên đề xuất một thay đổi thứ hai:

(MyType)myObj.myProp

thành

myObjRef

Điều này giúp tiết kiệm quyền truy cập thuộc tính và truyền, so với mã gốc. Nhưng nó chỉ có thể sau khi thay đổi isthành as.


@Default: Không, không phải. Điều đó không có nghĩa là nó không có trong mã.
Ben Voigt

1
xin lỗi .. hiểu lầm. tuy nhiên, (MyType)sẽ ném ra ngoại lệ nếu quá trình truyền không thành công. aschỉ trả lại null.
Mặc định

@Default: Quá trình truyền sẽ không thất bại, vì kiểu đã được kiểm tra is(mã đó nằm trong câu hỏi).
Ben Voigt

1
tuy nhiên, re # muốn thay thế mã đó - nghĩa là nó sẽ không ở đó sau thay đổi được đề xuất.
Mặc định

Tôi nghĩ rằng tôi đang làm theo suy nghĩ của bạn ở đây (chỉ cần tôi mất một chút thời gian). Ý bạn là dòng đầu tiên nằm ở đâu đó trong mã và dòng đó sẽ được đơn giản hóa sau gợi ý Re # đến dòng thứ hai?
Mặc định

0

Tôi muốn nói điều này là để tạo một phiên bản được đánh máy mạnh của myObj.myProp, là myObjRef. Điều này sau đó sẽ được sử dụng khi bạn đang tham chiếu giá trị này trong khối, thay vì phải thực hiện ép kiểu.

Ví dụ, điều này:

myObjRef.SomeProperty

tốt hơn thế này:

((MyType)myObj.myProp).SomeProperty
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.