Vì true
không phải là một kiểu chuỗi, thế nào là null + true
mộ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ì?
Vì true
không phải là một kiểu chuỗi, thế nào là null + true
mộ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ì?
Câu trả lời:
Đ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ử có 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 string
vì 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 string
khô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.
true
khô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.
x
thuộ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 bool
thành object
(cũng được), không phải string
.
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.
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
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.
Đ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.
null
sẽ đượ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 true
sẽ đượ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
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
...
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ó.