Null + true là một chuỗi như thế nào?


112

truekhông phải là một kiểu chuỗi, thế nào là null + truemột chuỗi?

string s = true;  //Cannot implicitly convert type 'bool' to 'string'   
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'

lý do đằng sau này là gì?


27
Bạn làm điều gì đó không có ý nghĩa và sau đó không thích thông báo mà trình biên dịch tạo ra? Đây là mã C # không hợp lệ ... chính xác thì bạn muốn nó làm gì?
Hogan

19
@Hogan: Đoạn mã có vẻ ngẫu nhiên và đủ học thuật để câu hỏi được đặt ra vì sự tò mò thuần túy. Chỉ là suy đoán của tôi ...
BoltClock

8
null + true trả về 1 trong JavaScript.
Fatih Acet

Câu trả lời:


147

Điều này có vẻ kỳ lạ, nó chỉ đơn giản là tuân theo các quy tắc từ thông số ngôn ngữ C #.

Từ phần 7.3.4:

Một phép toán có dạng x op y, trong đó op là một toán tử nhị phân có thể nạp chồng, x là một biểu thức của kiểu X và y là một biểu thức của kiểu Y, được xử lý như sau:

  • Tập hợp các toán tử ứng viên do người dùng xác định do X và Y cung cấp cho toán tử hoạt động op (x, y) được xác định. Tập hợp bao gồm liên hiệp các toán tử ứng viên do X cung cấp và toán tử ứng viên do Y cung cấp, mỗi toán tử được xác định bằng cách sử dụng các quy tắc của §7.3.5. Nếu X và Y là cùng một kiểu hoặc nếu X và Y có nguồn gốc từ một kiểu cơ sở chung, thì các toán tử ứng viên dùng chung chỉ xảy ra trong tập kết hợp một lần.
  • Nếu tập hợp các toán tử ứng viên do người dùng xác định không trống, thì đây sẽ trở thành tập hợp các toán tử ứng viên cho hoạt động. Nếu không, các triển khai op toán tử nhị phân được xác định trước, bao gồm cả các dạng đã nâng của chúng, sẽ trở thành tập hợp các toán tử ứng viên cho hoạt động. Các triển khai được xác định trước của một toán tử nhất định được chỉ định trong mô tả của toán tử (§7.8 đến §7.12).
  • Các quy tắc giải quyết quá tải của §7.5.3 được áp dụng cho tập hợp các toán tử ứng viên để chọn toán tử tốt nhất đối với danh sách đối số (x, y) và toán tử này trở thành kết quả của quá trình giải quyết quá tải. Nếu giải pháp quá tải không chọn được một toán tử tốt nhất, thì sẽ xảy ra lỗi thời gian ràng buộc.

Vì vậy, chúng ta hãy lần lượt đi qua vấn đề này.

X là kiểu rỗng ở đây - hoặc hoàn toàn không phải là kiểu, nếu bạn muốn nghĩ theo cách đó. Nó không cung cấp bất kỳ ứng viên nào. Y là bool, không cung cấp bất kỳ +toán tử nào do người dùng xác định . Vì vậy, bước đầu tiên không tìm thấy toán tử do người dùng định nghĩa.

Sau đó, trình biên dịch chuyển sang dấu đầu dòng thứ hai, xem xét các triển khai + toán tử nhị phân được xác định trước và các dạng đã nâng của chúng. Chúng được liệt kê trong phần 7.8.4 của thông số kỹ thuật.

Nếu bạn xem qua các toán tử được xác định trước đó, thì chỉ có một toán tử thể áp dụng được string operator +(string x, object y). Vì vậy, tập hợp ứng viên có một mục nhập duy nhất. Điều đó làm cho dấu đầu dòng cuối cùng rất đơn giản ... độ phân giải quá tải chọn toán tử đó, đưa ra một kiểu biểu thức tổng thể string.

Một điểm thú vị là điều này sẽ xảy ra ngay cả khi có các toán tử khác do người dùng xác định có sẵn trên các loại không được đề cập. Ví dụ:

// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;

Điều đó tốt, nhưng nó không được sử dụng cho một chữ rỗng, bởi vì trình biên dịch không biết để xem xét Foo. Nó chỉ biết để xem xét stringvì nó là một toán tử được xác định trước được liệt kê rõ ràng trong thông số kỹ thuật. (Trên thực tế, nó không phải là một toán tử được định nghĩa bởi kiểu chuỗi ... 1 ) Điều đó có nghĩa là nó sẽ không biên dịch được:

// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;

Tất nhiên, các kiểu toán hạng thứ hai khác sẽ sử dụng một số toán tử khác:

var x = null + 0; // x is Nullable<int>
var y = null + 0L; // y is Nullable<long>
var z = null + DayOfWeek.Sunday; // z is Nullable<DayOfWeek>

1 Bạn có thể tự hỏi tại sao không có chuỗi + toán tử. Đó là một câu hỏi hợp lý và tôi chỉ đoán câu trả lời, nhưng hãy xem xét biểu thức này:

string x = a + b + c + d;

Nếu stringkhông có cách viết hoa đặc biệt trong trình biên dịch C #, điều này sẽ kết thúc hiệu quả như sau:

string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;

Vì vậy, đó là tạo ra hai chuỗi trung gian không cần thiết. Tuy nhiên, bởi vì có sự hỗ trợ đặc biệt trong trình biên dịch, nó thực sự có thể biên dịch ở trên thành:

string x = string.Concat(a, b, c, d);

có thể tạo chỉ một chuỗi có độ dài chính xác, sao chép tất cả dữ liệu chính xác một lần. Đẹp.


'đưa ra một kiểu biểu thức tổng thể của chuỗi' Tôi chỉ đơn giản là không đồng ý. Lỗi đến từ việc truekhông thể chuyển đổi thành string. Nếu biểu thức hợp lệ, kiểu sẽ là string, nhưng trong trường hợp này, việc không chuyển đổi thành chuỗi làm cho toàn bộ biểu thức trở thành lỗi và do đó không có kiểu.
leppie

6
@leppie: Khái niệm hợp lệ, và loại của nó là chuỗi. Hãy thử "var x = null + true;" - nó biên dịch và xthuộc loại string. Lưu ý rằng chữ ký được sử dụng ở đây là string operator+(string, object)- nó đang chuyển đổi boolthành object(cũng được), không phải string.
Jon Skeet

Cảm ơn Jon, hiểu rồi. BTW phần 14.7.4 trong phiên bản 2 của thông số kỹ thuật.
leppie

@leppie: Đó có phải là đánh số ECMA không? Điều đó luôn rất khác biệt :(
Jon Skeet

47
Trong 20 phút. Anh ấy đã viết toàn bộ điều này trong 20 phút. Anh ấy nên viết sách hoặc một lúc nào đó ... ồ chờ đã.
Epaga

44

Lý do tại sao là vì một khi bạn giới thiệu các +quy tắc ràng buộc toán tử C # sẽ có hiệu lực. Nó sẽ xem xét tập hợp các +toán tử có sẵn và chọn mức quá tải tốt nhất. Một trong những toán tử đó là

string operator +(string x, object y)

Quá tải này tương thích với các kiểu đối số trong biểu thức null + true. Do đó, nó được chọn làm toán tử và được đánh giá về cơ bản là ((string)null) + trueđánh giá giá trị "True".

Phần 7.7.4 của thông số ngôn ngữ C # chứa các chi tiết xung quanh độ phân giải này.


1
Tôi nhận thấy IL được tạo ra đi thẳng đến việc gọi Concat. Tôi đã nghĩ rằng tôi sẽ thấy một cuộc gọi đến hàm toán tử + trong đó. Đó sẽ chỉ đơn giản là tối ưu hóa nội tuyến?
quentin-starin

1
@qstarin tôi tin rằng lý do là không có thực sự một operator+cho string. Thay vào đó nó chỉ tồn tại trong tâm trí của trình biên dịch và nó chỉ đơn giản chuyển nó xuống kêu gọistring.Concat
JaredPar

1
@qstarin @JaredPar: Tôi đã đi sâu vào vấn đề đó một chút trong câu trả lời của mình.
Jon Skeet

11

Trình biên dịch đi tìm toán tử + () có thể nhận đối số null trước. Không có loại giá trị tiêu chuẩn nào đủ điều kiện, null không phải là giá trị hợp lệ cho chúng. Đối sánh duy nhất là System.String.operator + (), không có gì mơ hồ.

Đối số thứ 2 của toán tử đó cũng là một chuỗi. Điều đó thật khó hiểu, không thể chuyển đổi hoàn toàn bool thành chuỗi.


10

Điều thú vị là sử dụng Reflector để kiểm tra những gì được tạo, đoạn mã sau:

string b = null + true;
Console.WriteLine(b);

được chuyển đổi thành điều này bởi trình biên dịch:

Console.WriteLine(true);

Tôi phải nói rằng lý do đằng sau "tối ưu hóa" này hơi kỳ lạ, và không phù hợp với lựa chọn toán tử mà tôi mong đợi.

Ngoài ra, mã sau:

var b = null + true; 
var sb = new StringBuilder(b);

được chuyển thành

string b = true; 
StringBuilder sb = new StringBuilder(b);

nơi string b = true;thực sự không được trình biên dịch chấp nhận.


8

nullsẽ được truyền sang chuỗi null, và có một bộ chuyển đổi ngầm định từ bool sang chuỗi vì vậy truesẽ được chuyển thành chuỗi và sau đó, +toán tử sẽ được áp dụng: nó giống như: string str = "" + true.ToString ();

nếu bạn kiểm tra nó bằng Ildasm:

string str = null + true;

nó như dưới đây:

.locals init ([0] string str)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  box        [mscorlib]System.Boolean
  IL_0007:  call       string [mscorlib]System.String::Concat(object)
  IL_000c:  stloc.0

5
var b = (null + DateTime.Now); // String
var b = (null + 1);            // System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etc
var b = (null + new Object()); // String | same with any ref type

Khùng?? Không, phải có lý do đằng sau nó.

Ai đó gọi Eric Lippert...


5

Lý do cho điều này là sự tiện lợi (nối các chuỗi là một nhiệm vụ phổ biến).

Như BoltClock đã nói, toán tử '+' được định nghĩa trên các kiểu số, chuỗi và cũng có thể được định nghĩa cho các kiểu riêng của chúng ta (nạp chồng toán tử).

Nếu không có toán tử '+' được nạp chồng trên các kiểu của đối số và chúng không phải là kiểu số, trình biên dịch sẽ mặc định là nối chuỗi.

Trình biên dịch sẽ chèn một lệnh gọi đến String.Concat(...)khi bạn nối bằng '+' và việc triển khai Concat gọi ToString trên mỗi đối tượng được truyền vào 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.