Hai dấu hỏi cùng nhau có ý nghĩa gì trong C #?


1630

Chạy qua dòng mã này:

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

Hai dấu hỏi có ý nghĩa gì, nó có phải là một loại toán tử ternary không? Thật khó để tìm kiếm trong Google.


50
Nó chắc chắn không phải là một toán tử ternary - nó chỉ có hai toán hạng! Nó hơi giống toán tử điều kiện ( ternary) nhưng toán tử hợp nhất null là toán tử nhị phân.
Jon Skeet

90
Tôi đã giải thích nó trong một cuộc phỏng vấn nơi mà nhà tuyển dụng tiềm năng trước đây đã bày tỏ sự nghi ngờ về khả năng C # của tôi, vì tôi đã sử dụng Java một cách chuyên nghiệp trong một thời gian trước đây. Họ đã không nghe về nó trước đó và đã không đặt câu hỏi về sự quen thuộc của tôi với C # sau đó :)
Jon Skeet

77
@Jon Skeet Đã không có một sử thi thất bại trong việc nhận ra kỹ năng kể từ khi anh chàng từ chối Beatles. :-) Từ giờ trở đi, chỉ cần gửi cho họ một bản sao của cuốn sách của bạn với một liên kết url đến hồ sơ SO của bạn được viết ở bìa bên trong.
Người giữ Iain

6
IainMH: Đối với những gì nó có giá trị, tôi đã không khá bắt đầu viết cuốn sách được nêu ra. (Hoặc có lẽ tôi chỉ đang làm việc ở chương 1 - đại loại như thế.) Phải thừa nhận rằng một tìm kiếm cho tôi sẽ nhanh chóng tìm thấy blog + bài viết của tôi, v.v.
Jon Skeet

7
Re: câu cuối cùng trong q - cho ref trong tương lai, SymbolHound là tuyệt vời cho loại điều này, ví dụ: Symbolhound.com/?q=%3F%3F&l=&e=&n=&u= [cho bất kỳ ai nghi ngờ - Tôi không liên kết trong dù sao đi nữa, giống như một công cụ tốt khi tôi tìm thấy một ...]
Steve Chambers

Câu trả lời:


2155

Đó là toán tử hợp nhất null và khá giống toán tử ternary (ngay lập tức nếu). Xem thêm ?? Toán tử - MSDN .

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

mở rộng tới:

FormsAuth = formsAuth != null ? formsAuth : new FormsAuthenticationWrapper();

mà mở rộng hơn nữa để:

if(formsAuth != null)
    FormsAuth = formsAuth;
else
    FormsAuth = new FormsAuthenticationWrapper();

Trong tiếng Anh, nó có nghĩa là "Nếu bất cứ thứ gì ở bên trái không phải là null, hãy sử dụng cái đó, nếu không thì sử dụng cái gì ở bên phải."

Lưu ý rằng bạn có thể sử dụng bất kỳ số nào trong số này theo thứ tự. Câu lệnh sau sẽ gán giá trị không null đầu tiên Answer#cho Answer(nếu tất cả các Câu trả lời là null thì Answerlà null):

string Answer = Answer1 ?? Answer2 ?? Answer3 ?? Answer4;

Ngoài ra, điều đáng nói trong khi việc mở rộng ở trên là tương đương về mặt khái niệm, kết quả của mỗi biểu thức chỉ được đánh giá một lần. Điều này rất quan trọng nếu ví dụ một biểu thức là một cuộc gọi phương thức với các tác dụng phụ. (Tín dụng cho @Joey vì đã chỉ ra điều này.)


17
Có khả năng nguy hiểm để xâu chuỗi những thứ này có thể
Coops

6
@CodeBlend thế nào vậy?
lc.

68
@CodeBlend, nó không nguy hiểm. Nếu bạn muốn mở rộng, bạn chỉ cần có một loạt các câu lệnh if / other lồng nhau. Cú pháp thật lạ vì bạn không quen nhìn thấy nó.
joelmdev

31
Nó cũng sẽ là một gửi thần nếu nó cũng hoạt động cho các chuỗi trống :)
Zyphrax

14
@Gusdor ??là liên kết trái, vì vậy a ?? b ?? c ?? dtương đương với ((a ?? b) ?? c ) ?? d. "Các toán tử gán và toán tử ternary (? :) là liên kết phải. Tất cả các toán tử nhị phân khác là liên kết trái." Nguồn: msdn.microsoft.com/en-us/l Library / ms173145.aspx
Mark E. Haase

284

Chỉ vì chưa có ai nói những lời kỳ diệu: đó là toán tử kết hợp null . Nó được định nghĩa trong phần 7.12 của đặc tả ngôn ngữ C # 3.0 .

Nó rất tiện dụng, đặc biệt là do cách nó hoạt động khi nó được sử dụng nhiều lần trong một biểu thức. Một biểu thức của mẫu:

a ?? b ?? c ?? d

sẽ đưa ra kết quả của biểu thức anếu nó không null, nếu không thì thử b, nếu không thì thử c, nếu không thì thử d. Nó ngắn mạch ở mọi điểm.

Ngoài ra, nếu loại dkhông phải là nullable, loại của toàn bộ biểu thức cũng không phải là nullable.


2
Tôi thích cách bạn đơn giản hóa ý nghĩa của nó thành "toán tử hợp nhất null". Đó là tất cả những gì tôi cần.
FrankO

2
Tiếp tuyến nhưng có liên quan: Bây giờ là Swift, nhưng nó rất khác: "Nhà điều hành hợp nhất Nil" developer.apple.com/l
Library / ios / document / Swift / Conceptionual / đánh

69

Đó là toán tử hợp nhất null.

http://msdn.microsoft.com/en-us/l Library / ms173224.aspx

Có, gần như không thể tìm kiếm trừ khi bạn biết nó được gọi là gì! :-)

EDIT: Và đây là một tính năng thú vị từ một câu hỏi khác. Bạn có thể xâu chuỗi chúng.

Các tính năng ẩn của C #?


33
Ít nhất bây giờ, thật dễ dàng để tìm kiếm nó, ngay cả khi bạn không biết tên, tôi chỉ googled "dấu hỏi kép c #"
stivlo

27

Cảm ơn mọi người, đây là lời giải thích ngắn gọn nhất mà tôi tìm thấy trên trang MSDN:

// y = x, unless x is null, in which case y = -1.
int y = x ?? -1;

4
Gợi ý này ở một khía cạnh quan trọng của ?? toán tử - nó được giới thiệu để hỗ trợ làm việc với các loại nullable. Trong ví dụ của bạn, "x" có kiểu "int?" (Không thể <int>).
Drew Noakes

9
@vitule không, nếu toán hạng thứ hai của toán tử hợp nhất null là không null, thì kết quả là không null (và -1chỉ là một đơn giản int, không thể rỗng).
Suzanne Dupéron

Tôi thực sự muốn nó làm việc như thế này. Tôi muốn làm điều này mọi lúc, nhưng tôi không thể vì biến tôi đang sử dụng không phải là loại không thể. Hoạt động trong coffeescript y = x? -1
PandaWood

@PandaWood Thật ra, bạn có thể. Nếu xlà loại int?, nhưng ylà loại int, bạn có thể viết int y = (int)(x ?? -1). Nó sẽ phân tích xthành một intnếu nó không null, hoặc gán -1cho ynếu xnull.
Johan Wintgens

21

??có để cung cấp một giá trị cho loại nullable khi giá trị là null. Vì vậy, nếu FormsAuth là null, nó sẽ trả về FormsAuthenticationWrapper () mới.


19

nhập mô tả hình ảnh ở đây

Hai dấu hỏi (??) chỉ ra rằng nó là toán tử Coalescing.

Toán tử hợp nhất trả về giá trị NON-NULL đầu tiên từ một chuỗi. Bạn có thể xem video youtube này thể hiện toàn bộ thực tế.

Nhưng hãy để tôi thêm nhiều hơn những gì video nói.

Nếu bạn thấy ý nghĩa tiếng Anh của việc kết hợp lại, nó nói rằng hợp nhất với nhau. Ví dụ dưới đây là một mã kết hợp đơn giản, chuỗi bốn chuỗi.

Vì vậy, nếu str1nullnó sẽ cố gắng str2, nếu str2nullnó sẽ cố gắng str3và vân vân cho đến khi nó tìm thấy một chuỗi với một giá trị khác null.

string final = str1 ?? str2 ?? str3 ?? str4;

Nói một cách đơn giản, toán tử Coalescing trả về giá trị NON-NULL đầu tiên từ một chuỗi.


Tôi thích cách giải thích này vì nó có sơ đồ, và sau đó câu cuối cùng giải thích nó bằng những thuật ngữ đơn giản như vậy! Nó làm cho nó dễ dàng để hiểu và tăng sự tự tin của tôi trong sử dụng. Cảm ơn!
Joshua K

14

Đó là bàn tay ngắn cho các nhà điều hành ternary.

FormsAuth = (formsAuth != null) ? formsAuth : new FormsAuthenticationWrapper();

Hoặc cho những người không làm ternary:

if (formsAuth != null)
{
  FormsAuth = formsAuth;
}
else
{
  FormsAuth = new FormsAuthenticationWrapper();
}

3
Tôi chỉ sửa lỗi chính tả của "ternary" nhưng thực sự toán tử mà bạn muốn nói là toán tử có điều kiện. Nó tình cờ là toán tử ternary duy nhất trong C #, nhưng tại một số điểm, họ có thể thêm một toán tử khác, tại thời điểm đó "ternary" sẽ mơ hồ nhưng "có điều kiện" sẽ không.
Jon Skeet

Đó là viết tắt cho một cái gì đó bạn có thể làm với toán tử ternary (có điều kiện). Ở dạng dài của bạn, cả test ( != null) và thứ hai formsAuth(sau ?) đều có thể bị thay đổi; ở dạng hợp nhất null, cả hai đều lấy các giá trị bạn đã cung cấp.
Bob Sammer

12

Nếu bạn quen thuộc với Ruby, nó ||=có vẻ giống với C # ??đối với tôi. Đây là một số Ruby:

irb(main):001:0> str1 = nil
=> nil
irb(main):002:0> str1 ||= "new value"
=> "new value"
irb(main):003:0> str2 = "old value"
=> "old value"
irb(main):004:0> str2 ||= "another new value"
=> "old value"
irb(main):005:0> str1
=> "new value"
irb(main):006:0> str2
=> "old value"

Và trong C #:

string str1 = null;
str1 = str1 ?? "new value";
string str2 = "old value";
str2 = str2 ?? "another new value";

4
x ||= ydesugars cho một cái gì đó giống như x = x || y, vì vậy ??thực sự giống với đồng bằng ||trong Ruby.
qerub

6
Lưu ý rằng ??chỉ quan tâm về null, trong khi các ||nhà điều hành trong Ruby, như trong hầu hết các ngôn ngữ, khoảng hơn null, falsehoặc bất cứ điều gì có thể được coi là một boolean với giá trị false(ví dụ như trong một số ngôn ngữ, ""). Đây không phải là một điều tốt hay xấu, chỉ đơn thuần là một sự khác biệt.
Tim S.

11

điều hành liên kết

nó tương đương với

FormsAuth = formsAUth == null ? new FormsAuthenticationWrapper() : formsAuth

11

Không có gì nguy hiểm về điều này. Trong thực tế, nó là đẹp. Bạn có thể thêm giá trị mặc định nếu đó là mong muốn, ví dụ:

int x = x1 ?? x2 ?? x3 ?? x4 ?? 0;

1
Vì vậy, x1, x2, x3 và x4 có thể là các loại Nullable, ví dụ: int? x1 = null;Có đúng không
Kevin Meredith

1
@KevinMeredith x1- x4PHẢI là loại không có giá trị : thật vô nghĩa khi nói, "kết quả là 0nếu đó x4là một giá trị mà nó không thể lấy được" ( null). "Loại không thể" ở đây bao gồm cả loại giá trị null và loại tham chiếu, tất nhiên. Đó là lỗi thời gian biên dịch nếu một hoặc nhiều biến được xâu chuỗi (trừ biến cuối cùng) không thể rỗng.
Bob Sammer

11

Như đã chỉ ra một cách chính xác trong nhiều câu trả lời là "toán tử hợp nhất null" ( ?? ), nói về điều đó bạn cũng có thể muốn kiểm tra anh em họ của nó là "Toán tử có điều kiện Null" ( ?. Hoặc ? [ ) Là một toán tử mà nhiều lần nó được sử dụng kết hợp với ??

Toán tử không có điều kiện

Được sử dụng để kiểm tra null trước khi thực hiện thao tác truy cập thành viên ( ?. ) Hoặc chỉ mục ( ? [ ). Các toán tử này giúp bạn viết ít mã hơn để xử lý kiểm tra null, đặc biệt là giảm dần vào cấu trúc dữ liệu.

Ví dụ:

// if 'customers' or 'Order' property or 'Price' property  is null,
// dollarAmount will be 0 
// otherwise dollarAmount will be equal to 'customers.Order.Price'

int dollarAmount = customers?.Order?.Price ?? 0; 

Cách cũ mà không có ? ?? làm điều này là

int dollarAmount = customers != null 
                   && customers.Order!=null
                   && customers.Order.Price!=null 
                    ? customers.Order.Price : 0; 

đó là dài dòng và cồng kềnh.


10

Chỉ để giải trí của bạn (biết bạn là tất cả các chàng trai C # ;-).

Tôi nghĩ nó bắt nguồn từ Smalltalk, nơi nó đã tồn tại nhiều năm. Nó được định nghĩa ở đó là:

trong đối tượng:

? anArgument
    ^ self

trong UndiniteObject (còn gọi là lớp của nil):

? anArgument
    ^ anArgument

Có cả phiên bản đánh giá (?) Và không đánh giá (??) về điều này.
Nó thường được tìm thấy trong các phương thức getter cho các biến riêng tư (ví dụ) lười biếng, được để lại cho đến khi thực sự cần thiết.


âm thanh như gói ViewState với một thuộc tính trên UserControl. Chỉ khởi tạo ở lần nhận đầu tiên, nếu nó không được đặt trước. =)
Seiti

9

Một số ví dụ ở đây về việc nhận các giá trị sử dụng liên kết là không hiệu quả.

Những gì bạn thực sự muốn là:

return _formsAuthWrapper = _formsAuthWrapper ?? new FormsAuthenticationWrapper();

hoặc là

return _formsAuthWrapper ?? (_formsAuthWrapper = new FormsAuthenticationWrapper());

Điều này ngăn cản đối tượng được tái tạo mỗi lần. Thay vì biến riêng còn lại null và một đối tượng mới được tạo trên mỗi yêu cầu, điều này đảm bảo biến riêng được gán nếu đối tượng mới được tạo.


Không phải là ??đánh giá ngắn? new FormsAuthenticationWrapper();được đánh giá nếu và chỉ khi _formsAuthWrapper là con số không.
MSalters

Vâng, đó là toàn bộ vấn đề. Bạn chỉ muốn gọi phương thức nếu biến là null. @MSalters
KingOfHypocites

8

Ghi chú:

Tôi đã đọc toàn bộ chủ đề này và nhiều chủ đề khác nhưng tôi không thể tìm thấy câu trả lời thấu đáo như thế này.

Qua đó tôi hoàn toàn hiểu được "tại sao nên sử dụng ?? và khi nào nên sử dụng ?? và cách sử dụng ??."

Nguồn:

Nền tảng giao tiếp Windows được phát hành bởi Craig McMurtry ISBN 0-672-32948-4

Các loại giá trị không thể xóa

Có hai trường hợp phổ biến trong đó người ta muốn biết liệu một giá trị đã được gán cho một thể hiện của một loại giá trị hay chưa. Đầu tiên là khi thể hiện một giá trị trong cơ sở dữ liệu. Trong trường hợp như vậy, người ta muốn có thể kiểm tra thể hiện để xác định xem một giá trị có thực sự có trong cơ sở dữ liệu hay không. Tình huống khác, phù hợp hơn với chủ đề của cuốn sách này, là khi thể hiện đại diện cho một mục dữ liệu nhận được từ một số nguồn từ xa. Một lần nữa, người ta muốn xác định từ trường hợp liệu một giá trị cho mục dữ liệu đó có được nhận hay không.

.NET Framework 2.0 kết hợp một định nghĩa kiểu chung cung cấp cho các trường hợp như thế này trong đó người ta muốn gán null cho một thể hiện của một loại giá trị và kiểm tra xem giá trị của thể hiện đó có phải là null không. Định nghĩa loại chung đó là System.Nullable, ràng buộc các đối số loại chung có thể được thay thế cho T thành các loại giá trị. Thể hiện của các loại được xây dựng từ System.Nullable có thể được gán một giá trị null; thật vậy, giá trị của chúng là null theo mặc định. Do đó, các loại được xây dựng từ System.Nullable có thể được gọi là các loại giá trị nullable. System.Nullable có một thuộc tính, Giá trị, theo đó giá trị được gán cho một thể hiện của một loại được xây dựng từ nó có thể được lấy nếu giá trị của thể hiện không phải là null. Do đó, người ta có thể viết:

System.Nullable<int> myNullableInteger = null;
myNullableInteger = 1;
if (myNullableInteger != null)
{
Console.WriteLine(myNullableInteger.Value);
}

Ngôn ngữ lập trình C # cung cấp một cú pháp viết tắt để khai báo các kiểu được xây dựng từ System.Nullable. Cú pháp đó cho phép một người viết tắt:

System.Nullable<int> myNullableInteger;

đến

int? myNullableInteger;

Trình biên dịch sẽ ngăn người ta cố gắng gán giá trị của loại giá trị null cho loại giá trị thông thường theo cách này:

int? myNullableInteger = null;
int myInteger = myNullableInteger;

Nó ngăn người ta làm như vậy bởi vì loại giá trị nullable có thể có giá trị null, mà nó thực sự sẽ có trong trường hợp này và giá trị đó không thể được gán cho loại giá trị thông thường. Mặc dù trình biên dịch sẽ cho phép mã này,

int? myNullableInteger = null;
int myInteger = myNullableInteger.Value;

Câu lệnh thứ hai sẽ khiến ngoại lệ bị ném vì mọi nỗ lực truy cập vào thuộc tính System.Nullable.Value là một hoạt động không hợp lệ nếu loại được xây dựng từ System.Nullable chưa được gán giá trị T hợp lệ, điều này không xảy ra trong trường hợp này trường hợp

Phần kết luận:

Một cách thích hợp để gán giá trị của loại giá trị nullable cho loại giá trị thông thường là sử dụng thuộc tính System.Nullable.HasValue để xác định xem giá trị hợp lệ của T có được gán cho loại giá trị null hay không:

int? myNullableInteger = null;
if (myNullableInteger.HasValue)
{
int myInteger = myNullableInteger.Value;
}

Một tùy chọn khác là sử dụng cú pháp này:

int? myNullableInteger = null;
int myInteger = myNullableInteger ?? -1;

Theo đó, số nguyên thông thường myInteger được gán giá trị của số nguyên nullable "myNullableInteger" nếu giá trị sau được gán một giá trị số nguyên hợp lệ; mặt khác, myInteger được gán giá trị -1.


1

Đó là một toán tử hợp nhất null hoạt động tương tự như một toán tử ternary.

    a ?? b  => a !=null ? a : b 

Một điểm thú vị khác cho điều này là, "Một loại không thể có thể chứa một giá trị hoặc nó có thể không được xác định" . Vì vậy, nếu bạn cố gắng gán loại giá trị nullable cho loại giá trị không nullable, bạn sẽ gặp lỗi thời gian biên dịch.

int? x = null; // x is nullable value type
int z = 0; // z is non-nullable value type
z = x; // compile error will be there.

Vì vậy, để làm điều đó bằng cách sử dụng ?? nhà điều hành:

z = x ?? 1; // with ?? operator there are no issues

1
FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

tương đương với

FormsAuth = formsAuth != null ? formsAuth : new FormsAuthenticationWrapper();

Nhưng điều thú vị về nó là bạn có thể xâu chuỗi chúng, như những người khác đã nói. Một điểm yếu không thể chạm tới là bạn thực sự có thể sử dụng nó để ném ngoại lệ.

A = A ?? B ?? throw new Exception("A and B are both NULL");

Thật tuyệt vời khi bạn đưa các ví dụ vào bài đăng của mình, mặc dù câu hỏi đang tìm kiếm một lời giải thích về những gì người vận hành là hoặc làm.
TGP1994

1

Các ??nhà điều hành được gọi là các nhà điều hành null-coalescing. Nó trả về toán hạng bên trái nếu toán hạng không rỗng; nếu không, nó trả về toán hạng bên phải.

int? variable1 = null;
int variable2  = variable1 ?? 100;

Đặt variable2thành giá trị của variable1, nếu variable1KHÔNG rỗng; mặt khác, nếu variable1 == null, đặt variable2thành 100.


0

Những người khác đã mô tả Null Coalescing Operatorkhá tốt. Đối với những người quan tâm, có một cú pháp rút gọn trong đó (câu hỏi SO):

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

tương đương với điều này:

FormsAuth ??= new FormsAuthenticationWrapper();

Một số tìm thấy nó dễ đọc và cô đọng hơ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.