chuyển đổi với hành vi lạ var / null


91

Cho đoạn mã sau:

string someString = null;
switch (someString)
{
    case string s:
        Console.WriteLine("string s");
        break;
    case var o:
        Console.WriteLine("var o");
        break;
    default:
        Console.WriteLine("default");
        break;
}

Tại sao câu lệnh chuyển đổi khớp được bật case var o?

Đó là sự hiểu biết của tôi case string skhông phù hợp khi s == nullvì (hiệu quả) (null as string) != nullđánh giá là sai. IntelliSense trên VS Mã nói với tôi rằng olà một stringlà tốt. Có suy nghĩ gì không?


Tương tự như: Trường hợp chuyển mạch C # 7 với các kiểm tra rỗng


9
Đã xác nhận. Tôi thích câu hỏi này, đặc biệt là với các quan sát đó ostring(khẳng định với Generics - tức là Foo(o)nơi Foo<T>(T template) => typeof(T).Name) - đó là một trường hợp rất thú vị nơi string xcư xử khác biệt so với var xngay cả khi xđang gõ (bởi trình biên dịch) nhưstring
Marc Gravell

7
Trường hợp mặc định là mã chết. Tin rằng chúng ta nên đưa ra một cảnh báo ở đó. Đang kiểm tra.
JaredPar

13
Thật kỳ lạ đối với tôi khi các nhà thiết kế C # quyết định cho phép vartrong bối cảnh này. Điều đó chắc chắn có vẻ giống như thứ mà tôi tìm thấy trong C ++, không phải trong một ngôn ngữ có mục đích dẫn dắt lập trình viên "vào hố sâu thành công". Ở đây, varvừa mơ hồ vừa vô ích, những thứ mà thiết kế C # thường cố gắng tránh.
Peter Duniho

1
@PeterDuniho Tôi sẽ không nói là vô dụng; biểu thức gửi đến switchcó thể không phát âm được - các loại ẩn danh, v.v.; và nó không mơ hồ - trình biên dịch biết rõ loại; nó chỉ là khó hiểu (với tôi ít nhất) rằng các nullquy tắc rất khác nhau!
Marc Gravell

1
@PeterDuniho thực tế thú vị - chúng tôi đã từng tìm kiếm các quy tắc chính thức của phép gán xác định từ đặc tả C # 1.2 và mã mở rộng minh họa có khai báo biến bên trong khối (hiện tại); nó chỉ di chuyển ra bên ngoài trong 2.0, sau đó quay trở lại bên trong khi vấn đề chụp rõ ràng.
Marc Gravell

Câu trả lời:


69

Bên trong một switchcâu lệnh so khớp mẫu sử dụng một casekiểu rõ ràng sẽ hỏi xem giá trị được đề cập thuộc kiểu cụ thể hay kiểu dẫn xuất. Nó tương đương chính xác vớiis

switch (someString) {
  case string s:
}
if (someString is string) 

Giá trị nullkhông có kiểu và do đó không thỏa mãn một trong hai điều kiện trên. Kiểu tĩnh someStringkhông có tác dụng trong cả hai ví dụ.

Các varloại mặc dù trong mô hình phù hợp với hoạt động như một thẻ hoang dã và sẽ phù hợp với bất kỳ giá trị bao gồm null.

Các defaulttrường hợp đây là mã chết. Các case var osẽ phù hợp với bất kỳ giá trị, null hoặc không null. Một trường hợp không mặc định luôn thắng một trường hợp mặc định do đó defaultsẽ không bao giờ được đánh. Nếu bạn nhìn vào IL, bạn sẽ thấy nó thậm chí không được phát ra.

Trong nháy mắt, có vẻ kỳ lạ khi điều này biên dịch mà không có bất kỳ cảnh báo nào (chắc chắn đã khiến tôi thất vọng). Nhưng điều này phù hợp với hành vi C # quay trở lại 1.0. Trình biên dịch cho phép defaultcác trường hợp ngay cả khi nó có thể chứng minh rằng nó sẽ không bao giờ bị tấn công. Hãy xem xét như một ví dụ sau:

bool b = ...;
switch (b) {
  case true: ...
  case false: ...
  default: ...
}

Ở đây defaultsẽ không bao giờ bị đánh (ngay cả đối với boolgiá trị đó không phải là 1 hoặc 0). Tuy nhiên, C # đã cho phép điều này kể từ 1.0 mà không có cảnh báo. Sự khớp mô hình chỉ phù hợp với hành vi này ở đây.


4
Vấn đề thực sự mặc dù là trình biên dịch "show" varlà loại stringkhi nó thực sự không (thật không chắc chắn những gì loại nên thừa nhận)
shmuelie

@shmuelie loại vartrong ví dụ được tính toán string.
JaredPar

5
@JaredPar cảm ơn vì những thông tin chi tiết ở đây; Cá nhân tôi sẽ hỗ trợ việc phát ra nhiều cảnh báo hơn ngay cả khi nó không làm như vậy trước đây, nhưng tôi hiểu những hạn chế của nhóm ngôn ngữ. Bạn đã bao giờ xem xét "chế độ thay đổi về mọi thứ" (có thể được bật theo mặc định), so với "chế độ khắc kỷ kế thừa" (tự chọn) chưa? có thểcsc /stiffUpperLip
Marc Gravell

3
@MarcGravell, chúng tôi có một tính năng được gọi là sóng cảnh báo nhằm giúp đưa ra các cảnh báo mới dễ dàng hơn, ít phức tạp hơn. Về cơ bản mỗi bản phát hành trình biên dịch là một làn sóng mới và bạn có thể chọn tham gia các cảnh báo qua / wave: 1, / wave: 2, / wave: all.
JaredPar

4
@JonathanDickinson Tôi không nghĩ điều đó hiển thị những gì bạn nghĩ. Điều đó chỉ cho thấy a nulllà một stringtham chiếu hợp lệ và bất kỳ stringtham chiếu nào (bao gồm null) có thể được truyền ngầm (bảo toàn tham chiếu) thành objecttham chiếu và bất kỳ objecttham chiếu nào nullcó thể được truyền tải thành công (rõ ràng) thành bất kỳ loại nào khác, vẫn đang tồn tại null. Không thực sự giống nhau về kiểu hệ thống trình biên dịch.
Marc Gravell

22

Tôi đang tập hợp nhiều bình luận trên twitter ở đây - điều này thực sự mới đối với tôi và tôi hy vọng rằng jaredpar sẽ tham gia với một câu trả lời toàn diện hơn, nhưng; phiên bản ngắn như tôi hiểu:

case string s:

được hiểu là if(someString is string) { s = (string)someString; ...hoặc if((s = (someString as string)) != null) { ... }- một trong hai điều đó liên quan đến một nullbài kiểm tra - không thành công trong trường hợp của bạn; ngược lại:

case var o:

nơi giải quyết biên dịch onhư stringchỉ đơn giản là o = (string)someString; ...- không nullkiểm tra, mặc dù thực tế là nó trông giống như trên bề mặt, chỉ với trình biên dịch cung cấp các loại.

cuối cùng:

default:

ở đây không thể đạt được , vì trường hợp trên bắt tất cả. Đây có thể là một lỗi của trình biên dịch trong đó nó không phát ra cảnh báo mã không thể truy cập được.

Tôi đồng ý rằng điều này rất tinh tế và nhiều sắc thái, và khó hiểu. Nhưng rõ ràng case var okịch bản có sử dụng với truyền null ( o?.Length ?? 0vv). Tôi đồng ý rằng thật kỳ lạ khi điều này hoạt động rất khác nhau giữa var ostring s, nhưng nó là những gì trình biên dịch hiện đang làm.


14

Đó là vì các case <Type>kết quả phù hợp với kiểu động (thời gian chạy), không phải kiểu tĩnh (thời gian biên dịch). nullkhông có kiểu động nên không thể so khớp với string. varchỉ là dự phòng.

(Đăng vì tôi thích câu trả lời ngắn.)

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.