2010-02-12 40 views
28

Cho rằng đây là một trường hợp sử dụng rất tự nhiên (nếu bạn không biết những gì as thực sự thực hiện),Tại sao 'được' triển khai thành 'dưới dạng'?

if (x is Bar) { 
    Bar y = x as Bar; 
    something(); 
} 

là có hiệu quả tương đương (có nghĩa là, trình biên dịch tạo CIL từ mã trên sẽ tương đương) tới:

Bar y = x as Bar; 
if (y != null) { 
    y = x as Bar; //The conversion is done twice! 
    something(); 
} 

EDIT:

tôi đoán tôi đã không được thực hiện câu hỏi của tôi rõ ràng. Tôi sẽ không bao giờ viết đoạn thứ hai vì nó tất nhiên là thừa. Tôi tuyên bố rằng CIL được tạo bởi trình biên dịch khi biên dịch đoạn mã đầu tiên tương đương với đoạn mã thứ hai, là thừa. Câu hỏi: a) Điều này có đúng không? b) Nếu vậy, tại sao is được triển khai như vậy?

Đây là bởi vì tôi tìm thấy đoạn đầu tiên rõ ràng hơn rất nhiều và đẹp hơn thực sự tốt bằng văn bản

Bar y = x as Bar; 
if (y != null) { 
    something(); 
} 

KẾT LUẬN:

Tối ưu hóa các trường hợp is/as không được trách nhiệm của trình biên dịch, nhưng của JIT.

Ngoài ra, với kiểm tra không có hướng dẫn ít hơn (và ít tốn kém hơn cả hai phương án thay thế (isasiscast).

Phụ Lục:

CIL cho như với nullcheck (.NET 3.5):

L_0001: ldarg.1 
L_0002: isinst string 
L_0007: stloc.0 
L_0008: ldloc.0 
L_0009: ldnull 
L_000a: ceq 
L_000c: stloc.1 
L_000d: ldloc.1 
L_000e: brtrue.s L_0019 
L_0011: ldarg.0 
L_0019: ret 

CIL là và dàn diễn viên (.NET 3.5):

L_0001: ldarg.1 
L_0002: isinst string 
L_0007: ldnull 
L_0008: cgt.un 
L_000a: ldc.i4.0 
L_000b: ceq 
L_000d: stloc.1 
L_000e: ldloc.1 
L_000f: brtrue.s L_0021 
L_0012: ldarg.1 
L_0013: castclass string 
L_0018: stloc.0 
L_0019: ldarg.0 
L_0021: ret 

CIL là và dưới dạng (.NET 3.5):

L_0001: ldarg.1 
L_0002: isinst string 
L_0007: ldnull 
L_0008: cgt.un 
L_000a: ldc.i4.0 
L_000b: ceq 
L_000d: stloc.1 
L_000e: ldloc.1 
L_000f: brtrue.s L_0021 
L_0012: ldarg.1 
L_0013: isinst string 
L_0018: stloc.0 
L_0019: ldarg.0 
L_0021: ret 

Những điều này đã được chỉnh sửa cho sự ngắn gọn (các khai báo phương thức, các bước nhảy và các cuộc gọi tới một cái gì đó() đã được gỡ bỏ).

+4

Tôi sẽ không gọi đó là một usecase điển hình, điển hình hơn sẽ là Bar y = x là Bar; if (y! = null) {do_stuff(); }. Nếu bạn đang sử dụng _as_ anyways, tại sao lại kiểm tra bằng _is_ trước? – dbemerlin

+0

@dbemerlin: Bởi vì đoạn đầu tiên tự nhiên và dễ chịu hơn mắt nhiều so với giây. Đây chính là quan điểm của tôi. –

+5

Người đầu tiên đọc cho tôi như thể nó được viết bởi một người không biết những gì 'làm', thành thật mà nói. – AakashM

Trả lời

12

a) Đây có phải là đúng

Vâng, mặc dù tôi sẽ nói nó theo cách khác. Bạn đang nói rằng "là" là một đường cú pháp cho như-theo-by-null-kiểm tra. Tôi đã có thể nói nó theo cách khác: rằng "như" là một cú pháp đường cho "kiểm tra để thực hiện loại, đúc nếu thành công, null nếu thất bại".

Đó là để nói, tôi sẽ nghiêng nhiều hơn để nói

if (x is Bar) { 
    Bar y = x as Bar; 
    something(); 
} 

là có hiệu quả tương đương với

if (x is Bar) { 
    Bar y = (x is Bar) ? (Bar)x : (Bar) null; 
    something(); 
} 

Xem, bạn muốn xác định "là" về "là", Không phải hướng ngược lại. Câu hỏi thực sự nên là "tại sao được thực hiện như là?" :-)

b) Nếu vậy, tại sao được triển khai như vậy?

Vì đó là việc triển khai chính xác đặc điểm kỹ thuật.

Tôi nghĩ rằng tôi không theo dòng suy nghĩ của bạn ở đây. Có điều gì sai với việc triển khai đó không? Bạn muốn nó được triển khai như thế nào? Bạn có các hướng dẫn "isinst" và "castclass" theo ý của bạn; mô tả codegen cho chương trình của bạn mà bạn muốn xem.

+0

Không có gì SAU với việc thực hiện. Nó chỉ làm cho một trường hợp sử dụng cụ thể cá nhân tôi thích tốt hơn so với thay thế chính xác (tôi nghĩ rằng nó dễ đọc hơn và trực quan) không hiệu quả. Vì vậy, tôi tự hỏi nếu có một lý do cho điều đó. Và sau đó, để làm cho nó hoạt động cho trường hợp sử dụng này, trình biên dịch nên thực hiện một tối ưu hóa để bảo toàn thể hiện được sử dụng trong bài kiểm tra: if (x là Bar) {Bar y = x as Bar; // Tôi ước rằng ở đây cùng một ví dụ được sử dụng trong bài kiểm tra nếu được sử dụng}. Nhưng đó có thể không phải là chủ đề an toàn trong một số tình huống và có thể sẽ có một số vấn đề khác ... –

+0

@Vinko: Nhưng cùng một ví dụ được sử dụng. Nó luôn luôn là cùng một tham chiếu đến cùng một ví dụ. Tôi vẫn không theo bạn. Bạn đang nói rằng bạn không muốn thử nghiệm loại thực hiện ba lần? Bởi vì nó được thực hiện ba lần trong trường hợp thành công ở đây - một lần vào lần đầu tiên "là", một khi trên "là" là một phần của "như", và một lần trong dàn diễn viên. Nếu bạn không muốn thử nghiệm dự phòng, thì không sử dụng "as" trong hệ quả. Nói "Bar y = (Bar) x;". Sau đó, bạn chỉ nhận được kiểm tra thực hiện hai lần. –

+0

Mặc dù điều này vẫn không giải thích tại sao không có tối ưu hóa trong trường hợp đầu tiên với và là. Hay là có? –

2

Bạn sẽ không thực hiện giây thứ hai y = x as Bar;, vì bạn đã có y là Bar.

+0

Tôi sẽ không, nhưng nếu tôi viết nó theo cách tự nhiên hơn (is), trình biên dịch sẽ là –

5

Trong ví dụ của bạn, việc sử dụng as là không cần thiết. Vì bạn đã biết rằng x is Bar, bạn nên sử dụng một dàn diễn viên:

if (x is Bar) 
{ 
    Bay y = (Bar)x; 
} 

Ngoài ra, chuyển đổi sử dụng as và chỉ cần kiểm tra cho null:

Bar y = x as Bar; 
if (y != null) 
{ 

} 
+0

Điểm của câu hỏi là trình biên dịch tạo ra mã dự phòng. –

+0

Ví dụ đầu tiên làm cho hai phôi, trong khi thứ hai chỉ có một, và phôi là tốn kém. Vì vậy, tôi thích cách tiếp cận thứ hai khi Bar là một lớp học. (Đối với các loại giá trị chỉ có thể sử dụng loại đầu tiên, dĩ nhiên.) – treaschf

5

Trước hết tôi không đồng ý với giả thuyết của mình rằng đây là điển hình hơn trường hợp sử dụng.Nó có thể là phương pháp ưa thích của bạn, nhưng cách tiếp cận thành ngữ là "như kiểm tra null +" phong cách:

Bar y = x as Bar; 
if (y != null) { 
    something(); 
} 

Như bạn đã tìm thấy "được" cách tiếp cận đòi hỏi thêm "như" hoặc một dàn diễn viên, đó là lý do "như" với kiểm tra null là cách tiêu chuẩn để làm điều này trong kinh nghiệm của tôi.

Tôi thấy không có gì xúc phạm về cách tiếp cận "như", cá nhân tôi không nghĩ rằng nó khó chịu hơn trên mắt so với bất kỳ mã nào khác.

Đối với câu hỏi thực tế, tại sao từ khóa is được thực hiện theo từ khóa as, tôi không biết, nhưng tôi thích chơi chữ trong câu hỏi của bạn :) Tôi nghi ngờ không thực sự được triển khai khác, nhưng công cụ (Reflector tôi đoán) bạn sử dụng để tạo ra C# từ IL giải thích IL về as.

+0

OK, tôi đã thay đổi trường hợp sử dụng "rất tự nhiên" hơn. Tôi không tìm thấy gì đặc biệt xúc phạm về việc kiểm tra null, tôi chỉ tìm thấy đoạn mã khác đẹp hơn rất nhiều :) –

+1

Chắc chắn, tôi không có ý chỉ trích quan điểm của bạn, nhưng tôi nghĩ phong cách "như" là nhiều hơn hoặc ít cách tiếp cận tiêu chuẩn hơn. –

9

Vâng, lệnh IL có sẵn (isinst) sẽ trả về một đối tượng thuộc loại thích hợp hoặc null nếu không thể chuyển đổi được. Và nó không ném một ngoại lệ nếu chuyển đổi là không thể.

Cho rằng, cả "là" và "như" là tầm thường để triển khai. Tôi sẽ không tuyên bố rằng "được" được thực hiện như "như" trong trường hợp này, chỉ là lệnh IL cơ bản cho phép cả hai xảy ra. Bây giờ, tại sao trình biên dịch không thể tối ưu hóa "is" theo sau là "as" thành một cuộc gọi isinst duy nhất, đó là một vấn đề khác. Có lẽ, trong trường hợp này, nó liên quan đến phạm vi biến (mặc dù vào thời điểm này là IL, phạm vi không thực sự tồn tại)

Sửa

On suy nghĩ thứ hai, bạn không thể tối ưu hóa "được "theo sau là" dưới dạng "thành một cuộc gọi isinst duy nhất, mà không biết rằng biến được thảo luận không phải là chủ đề cập nhật từ các chủ đề khác.

Giả sử x là một chuỗi:

//Thread1 
if(x is string) 

//Thread2 
x = new ComplexObject(); 

//Thread1 
    y = x as string 

Ở đây, y nên được null.

+1

Đây thực sự là một lý do. Cảm ơn! :) –

+2

Miễn là biến không dễ bay hơi và không có rào cản ở giữa và là hoạt động, không có lý do gì để không sử dụng lại trường hợp đầu tiên. – erikkallen

+0

@erikkallen - Tôi sẽ không đồng ý. Tôi chỉ nghĩ rằng nó sẽ là một sự tối ưu hóa đặc biệt để tạo ra, đặc biệt là việc sử dụng "bình thường" là "là" và "như" –

-1

tôi nghi ngờ mạnh mẽ rằng nhanh hơn như và không đòi hỏi một phân bổ. Vì vậy, nếu x hiếm khi là Bar, thì đoạn đầu tiên là tốt. Nếu x chủ yếu là Thanh, thì bạn nên sử dụng làm vì không yêu cầu diễn viên thứ hai. Nó phụ thuộc vào cách sử dụng và hoàn cảnh của mã.

+0

ai sẽ "làm" phân bổ? con trỏ nó trả về sẽ nằm trên stack như một con trỏ là kiểu ** value **. –

+0

'as' không yêu cầu phân bổ, nếu đối tượng dưới sự giám sát thực hiện các đặc tính cần thiết (tức là thực hiện giao diện hoặc là một phân lớp) con trỏ được trả về, ngược lại con trỏ null được trả về. – flindeberg

1

Theo bài đăng blog How Many Passes? của Eric Lippert, đó là một trình biên dịch. Để báo giá:

Sau đó, chúng tôi chạy thẻ tối ưu hóa viết lại tầm thường "is" và "as" nhà khai thác.

Vì vậy, có lẽ đó là lý do tại sao bạn thấy cùng một CIL được tạo cho cả hai đoạn mã.

+0

Liên kết thú vị, cảm ơn. –

1

Bạn có thể viết mã ngay bây giờ như

DoIfOfType<Bar>(possibleBar, b => b.something()) 

rằng tôi sẽ nói là một chút rõ ràng hơn, nhưng không phải là nhanh chóng mà không kỳ diệu thực sự từ trình biên dịch.

0

Phạm vi của 'y' sẽ giảm nếu bạn đặt tờ khai bên trong vòng lặp.

Ai đã viết nó có thể thích truyền 'x là T' nhiều hơn '(T) x' và muốn giới hạn phạm vi 'y'.

0

Bạn quên các loại giá trị. Ví dụ:

static void Main(string[] args) 
    { 
     ValueType vt; 
     FooClass f = vt as FooClass; 

    } 

    private class FooClass 
    { 
     public int Bar { get; set; } 
    } 

Sẽ không biên dịch thành loại giá trị không thể chuyển đổi như thế này.

+0

Tôi không hiểu làm thế nào điều này là có liên quan ở đây. Tất nhiên tôi đang nói về nơi nó có thể được sử dụng ... cẩn thận để xây dựng? –

+0

Có liên quan vì đó là lý do tại sao bạn không thể loại bỏ toán tử 'is'. –

Các vấn đề liên quan