Các cách duy nhất để sử dụng toán tử Null Coalescing [đã đóng]


164

Tôi biết cách tiêu chuẩn sử dụng toán tử kết hợp Null trong C # là đặt giá trị mặc định.

string nobody = null;
string somebody = "Bob Saget";
string anybody = "";

anybody = nobody   ?? "Mr. T"; // returns Mr. T
anybody = somebody ?? "Mr. T"; // returns "Bob Saget"

Nhưng những gì khác có thể ??được sử dụng cho? Nó dường như không hữu ích như toán tử ternary, ngoài việc ngắn gọn và dễ đọc hơn:

nobody = null;
anybody = nobody == null ? "Bob Saget" : nobody; // returns Bob Saget

Vì vậy, càng ít biết về toán tử hợp nhất null ...

  • Bạn đã sử dụng ??cho một cái gì khác?

  • ??cần thiết, hoặc bạn chỉ nên sử dụng toán tử ternary (mà hầu hết đều quen thuộc)

Câu trả lời:


216

Chà, trước hết, việc xâu chuỗi dễ dàng hơn nhiều so với chim nhạn tiêu chuẩn:

string anybody = parm1 ?? localDefault ?? globalDefault;

so với

string anyboby = (parm1 != null) ? parm1 
               : ((localDefault != null) ? localDefault 
               : globalDefault);

Nó cũng hoạt động tốt nếu đối tượng có thể null không phải là một biến:

string anybody = Parameters["Name"] 
              ?? Settings["Name"] 
              ?? GlobalSetting["Name"];

so với

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];

12
Chuỗi là một điểm cộng lớn cho nhà điều hành, loại bỏ một loạt các IF dư thừa
chakrit

1
Tôi chỉ sử dụng nó ngày hôm nay để thay thế một khối IF đơn giản mà tôi đã viết trước khi tôi biết về toán tử kết hợp ternary hoặc null. Các nhánh đúng và sai của câu lệnh IF ban đầu được gọi là cùng một phương thức, thay thế một trong các đối số của nó bằng một giá trị khác nếu một đầu vào nhất định là NULL. Với toán tử hợp nhất null, đó là một cuộc gọi. Điều này thực sự mạnh mẽ khi bạn có một phương pháp cần hai hoặc nhiều sự thay thế như vậy!
David A. Gray

177

Tôi đã sử dụng nó như một lớp lót lười biếng:

public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

Có đọc được không? Quyết định cho chính mình.


6
Hmmm, bạn đã tìm thấy một ví dụ cho "tại sao ai đó muốn sử dụng nó như một IF bị che giấu" ... điều đó thực sự rất dễ đọc đối với tôi.
Godeke

6
Tôi có thể đang thiếu một cái gì đó (tôi chủ yếu sử dụng Java), nhưng không có điều kiện chủng tộc ở đó?
Justin K

9
@Justin K - Chỉ có một điều kiện chủng tộc nếu nhiều luồng truy cập vào thuộc tính LazyProp của cùng một đối tượng. Nó có thể dễ dàng sửa chữa với một khóa, nếu yêu cầu an toàn của từng trường hợp. Rõ ràng trong ví dụ này, nó không bắt buộc.
Jeffrey L Whitledge

5
@Jeffrey: Nếu nó rõ ràng, tôi sẽ không đặt câu hỏi. :) Khi tôi thấy ví dụ đó, tôi nghĩ ngay đến một thành viên độc thân, và vì tôi tình cờ thực hiện hầu hết mã hóa của mình trong một môi trường đa luồng ... Nhưng, vâng, nếu chúng ta giả sử mã là chính xác, thì mọi thứ đều không cần thiết.
Justin K

7
Nó không phải là một Singleton để có một điều kiện chủng tộc. Chỉ là một thể hiện chung của lớp có LazyProp và nhiều luồng truy cập LazyProp. Lazy <T> là một cách tốt hơn để thực hiện loại điều này và theo mặc định là chủ đề an toàn (bạn có thể chọn để sửa đổi sự an toàn của chủ đề của Lazy <T>).
Niall Connaughton

52

Tôi đã thấy nó hữu ích theo hai cách "hơi kỳ lạ":

  • Thay thế cho việc có một outtham số khi viết TryParsecác thói quen (nghĩa là trả về giá trị null nếu phân tích cú pháp không thành công)
  • Là một đại diện "không biết" để so sánh

Sau này cần thêm một chút thông tin. Thông thường, khi bạn tạo một so sánh với nhiều yếu tố, bạn cần xem liệu phần đầu tiên của so sánh (ví dụ: tuổi) có đưa ra câu trả lời dứt khoát hay không, sau đó phần tiếp theo (ví dụ như tên) chỉ khi phần đầu tiên không giúp ích. Sử dụng toán tử hợp nhất null có nghĩa là bạn có thể viết các phép so sánh khá đơn giản (cho dù để đặt hàng hay bình đẳng). Ví dụ: sử dụng một vài lớp trợ giúp trong MiscUtil :

public int Compare(Person p1, Person p2)
{
    return PartialComparer.Compare(p1.Age, p2.Age)
        ?? PartialComparer.Compare(p1.Name, p2.Name)
        ?? PartialComparer.Compare(p1.Salary, p2.Salary)
        ?? 0;
}

Phải thừa nhận rằng bây giờ tôi có ProjectionComparer trong MiscUtil, cùng với một số tiện ích mở rộng, điều này làm cho loại điều này thậm chí còn dễ dàng hơn - nhưng nó vẫn gọn gàng.

Điều tương tự có thể được thực hiện để kiểm tra sự bằng nhau tham chiếu (hoặc vô hiệu) khi bắt đầu thực hiện Equals.


Tôi thích những gì bạn đã làm với PartialComparer, nhưng đang tìm kiếm các trường hợp tôi cần giữ các biến biểu thức được đánh giá. Tôi không rành về lambdas và các phần mở rộng, vì vậy bạn có thể xem liệu những điều sau đây có tuân thủ một mô hình tương tự không (tức là nó có hoạt động không)? stackoverflow.com/questions/1234263/#1241780
maxwellb

33

Một lợi thế khác là toán tử ternary yêu cầu đánh giá kép hoặc biến tạm thời.

Hãy xem xét điều này, ví dụ:

string result = MyMethod() ?? "default value";

trong khi với toán tử ternary, bạn còn lại một trong hai:

string result = (MyMethod () != null ? MyMethod () : "default value");

mà gọi MyMethod hai lần, hoặc:

string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");

Dù bằng cách nào, toán tử hợp nhất null sạch hơn và, tôi đoán là hiệu quả hơn.


1
+1. Đây là một lý do lớn tại sao tôi thích toán tử hợp nhất null. Nó đặc biệt hữu ích khi gọi MyMethod()có bất kỳ loại tác dụng phụ nào.
một CVn

Nếu MyMethod()không có bất kỳ hiệu ứng nào ngoài việc trả về một giá trị, trình biên dịch biết không gọi nó hai lần, vì vậy bạn thực sự không phải lo lắng về hiệu quả ở đây trong phần lớn các trường hợp.
TinyTimZamboni

Nó cũng giữ cho IMHO dễ đọc hơn khi MyMethod()có một chuỗi các đối tượng chấm. Vd:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore 26/03/13

@TinyTimZamboni, bạn có tài liệu tham khảo cho hành vi này của trình biên dịch không?
Kuba Wyrostek

@KubaWyrostek Tôi không có kiến ​​thức về hoạt động cụ thể của trình biên dịch C #, nhưng tôi có một số kinh nghiệm với lý thuyết trình biên dịch tĩnh với llvm. Có một số cách tiếp cận mà trình biên dịch có thể thực hiện để tối ưu hóa cuộc gọi như thế này. Đánh số giá trị toàn cầu sẽ nhận thấy rằng hai cuộc gọi MyMethodgiống hệt nhau trong ngữ cảnh này, giả sử đó MyMethodlà một hàm Pure. Một tùy chọn khác là Ghi nhớ tự động hoặc chỉ có chức năng đóng trong bộ đệm. Mặt khác: vi.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni

23

Một điều khác cần xem xét là toán tử hợp nhất không gọi phương thức get của một thuộc tính hai lần, như cách thức làm.

Vì vậy, có những kịch bản mà bạn không nên sử dụng ternary, ví dụ:

public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get 
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

Nếu bạn dùng:

var a = new A();
var b = a.Prop == null ? 0 : a.Prop;

getter sẽ được gọi hai lần và countbiến sẽ bằng 2 và nếu bạn sử dụng:

var b = a.Prop ?? 0

các countbiến sẽ được bình đẳng là 1, như nó phải.


4
Điều này xứng đáng được nâng cao hơn. Tôi đã đọc sooo nhiều lần mà ??tương đương với ?:.
Kuba Wyrostek

1
Điểm hợp lệ về một getter được gọi hai lần. Nhưng ví dụ này tôi sẽ xem xét một người thiết kế tồi để có được getter được đặt tên sai lệch như vậy để thực sự thay đổi đối tượng.
Linas

15

Ưu điểm lớn nhất mà tôi tìm thấy cho ??toán tử là bạn có thể dễ dàng chuyển đổi các loại giá trị null thành các loại không nullable:

int? test = null;
var result = test ?? 0; // result is int, not int?

Tôi thường xuyên sử dụng điều này trong các truy vấn Linq:

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?

Tôi có thể hơi muộn ở đây, nhưng ví dụ thứ hai sẽ ném nếu kvp == null. Và thực sự Nullable<T>có một GetValueOrDefaultphương pháp mà tôi thường sử dụng.
CompuChip

6
KeyValuePair là một loại giá trị trong khung .NET, vì vậy việc truy cập bất kỳ thuộc tính nào của nó sẽ không bao giờ ném ngoại lệ tham chiếu null. msdn.microsoft.com/en-us/l Library / 5tbh8a42 (v = vs.110) .aspx
Ryan

9

Tôi đã sử dụng ?? trong triển khai IDataErrorInfo của tôi:

public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

public string this[string columnName]
{
    get { ... }
}

Nếu bất kỳ thuộc tính riêng lẻ nào ở trạng thái "lỗi", tôi sẽ gặp lỗi đó, nếu không tôi sẽ nhận được null. Hoạt động thực sự tốt.


Hấp dẫn. Bạn đang sử dụng "cái này" làm tài sản. Tôi chưa bao giờ làm điều đó.
Armstrongest

Vâng, đó là một phần trong cách IDataErrorInfo hoạt động. Nói chung cú pháp đó chỉ hữu ích trên các lớp bộ sưu tập.
Matt Hamilton

4
Bạn đang lưu trữ các thông báo lỗi trong this["Name"], this["Address"], vv ??
Andrew

7

Bạn có thể sử dụng toán tử hợp nhất null để làm cho nó sạch hơn một chút để xử lý trường hợp không đặt tham số tùy chọn:

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...

Sẽ không tuyệt sao nếu dòng này có thể được viết là arg ?= Arg.Default?
Jesse de Wit

6

Tôi thích sử dụng toán tử hợp nhất null để lười tải các thuộc tính nhất định.

Một ví dụ rất đơn giản (và có thể) chỉ để minh họa quan điểm của tôi:

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}

Resharper thực sự sẽ đề xuất điều này như một công cụ tái cấu trúc cho một tải lười biếng "truyền thống".
arichards

5

Là ?? cần thiết, hoặc bạn chỉ nên sử dụng toán tử ternary (mà hầu hết đều quen thuộc)

Trên thực tế, kinh nghiệm của tôi là tất cả quá ít người quen thuộc với toán tử ternary (hay chính xác hơn là toán tử có điều kiện ; ?:là "ternary" theo cùng một nghĩa ||là nhị phân hoặc +là đơn nhất hoặc nhị phân, tuy nhiên nó thực sự là chỉ toán tử ternary trong nhiều ngôn ngữ), vì vậy ít nhất trong mẫu giới hạn đó, câu lệnh của bạn thất bại ngay tại đó.

Ngoài ra, như đã đề cập trước đây, có một tình huống chính khi toán tử hợp nhất null rất hữu ích và đó là bất cứ khi nào biểu thức được đánh giá có bất kỳ tác dụng phụ nào. Trong trường hợp đó, bạn không thể sử dụng toán tử điều kiện mà không (a) đưa ra một biến tạm thời hoặc (b) thay đổi logic thực tế của ứng dụng. (b) rõ ràng là không phù hợp trong mọi trường hợp và trong khi đó là sở thích cá nhân, tôi không thích làm lộn xộn phạm vi khai báo với nhiều biến số ngoại lai, ngay cả khi tồn tại trong thời gian ngắn, vì vậy (a) cũng không phù hợp kịch bản cụ thể.

Tất nhiên, nếu bạn cần thực hiện nhiều kiểm tra về kết quả, toán tử có điều kiện hoặc một tập hợp các ifkhối, có thể là công cụ cho công việc. Nhưng đối với đơn giản "nếu điều này là null, hãy sử dụng nó, nếu không sử dụng nó", toán tử hợp nhất null ??là hoàn hảo.


Nhận xét rất muộn từ tôi - nhưng rất vui khi thấy ai đó nói rằng một toán tử ternary là một toán tử có ba đối số (trong đó hiện có nhiều hơn một trong C #).
Steve Kidd

5

Một điều tôi đã làm gần đây là sử dụng kết hợp null để sao lưu as. Ví dụ:

object boxed = 4;
int i = (boxed as int?) ?? 99;

Console.WriteLine(i); // Prints 4

Nó cũng hữu ích để sao lưu chuỗi dài ?.mà mỗi lần có thể thất bại

int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there";

4

Vấn đề duy nhất là toán tử hợp nhất null không phát hiện các chuỗi rỗng.


I E

string result1 = string.empty ?? "dead code!";

string result2 = null ?? "coalesced!";

ĐẦU RA:

result1 = ""

result2 = coalesced!

Tôi hiện đang tìm cách ghi đè lên ?? Toán tử để làm việc xung quanh này. Nó chắc chắn sẽ hữu ích khi được tích hợp sẵn trong Framework này.

Suy nghĩ?


Bạn có thể làm điều này với các phương thức Tiện ích mở rộng, nhưng tôi đồng ý, nó sẽ là một bổ sung tốt cho mã và rất hữu ích trong ngữ cảnh web.
Armstrongest

Vâng, đây là một tình huống thường xuyên ... thậm chí còn có một phương thức đặc biệt String.IsNullOrEmpty (chuỗi) ...
Max Galkin

12
"toán tử hợp nhất null không phát hiện các chuỗi rỗng." Vâng nó là -coalescing điều hành, không phải là nullOrEmpty -coalescing điều hành. Và cá nhân, tôi khinh thường việc trộn lẫn các giá trị rỗng và rỗng trong các ngôn ngữ phân biệt giữa hai thứ, điều này làm cho việc giao thoa với những thứ không gây khó chịu. Và tôi hơi bị ám ảnh, vì vậy điều đó làm tôi khó chịu khi ngôn ngữ / cách triển khai không phân biệt giữa hai dù sao, ngay cả khi tôi hiểu lý do tại sao (như trong [hầu hết các triển khai của?] SQL).
JAB

3
??không thể bị quá tải: msdn.microsoft.com/en-us/l Library / 8edha89s (v = vs.100) .aspx - đó sẽ là một điều tuyệt vời để có thể quá tải mặc dù. Tôi sử dụng kết hợp: s1.Nullify() ?? s2.Nullify()trong đó string Nullify(this s)trả về a nulltrong trường hợp khi chuỗi rỗng.
Bộ

Vấn đề duy nhất? Tôi chỉ thấy mình muốn ?? = và tìm thấy chủ đề này trong khi xem liệu có cách nào để làm điều đó. (Tình huống: Vượt qua đầu tiên đã tải các trường hợp ngoại lệ, bây giờ tôi muốn quay lại và tải các giá trị mặc định vào bất cứ thứ gì chưa được tải.)
Loren Pechtel

3

Là ?? cần thiết, hoặc bạn chỉ nên sử dụng toán tử ternary (mà hầu hết đều quen thuộc)

Bạn nên sử dụng những gì thể hiện tốt nhất ý định của bạn. Kể từ đó một nhà điều hành liên hiệp null, sử dụng nó .

Mặt khác, vì nó rất chuyên dụng, tôi không nghĩ nó có công dụng khác. Tôi sẽ thích sự quá tải thích hợp của ||toán tử, như các ngôn ngữ khác làm. Điều này sẽ được đánh giá cao hơn trong thiết kế ngôn ngữ. Nhưng cũng rất tốt


3

Mát mẻ! Hãy coi tôi là một người không biết về toán tử hợp nhất null - đó là thứ khá tiện lợi.

Tôi thấy nó dễ đọc hơn nhiều so với toán tử ternary.

Nơi đầu tiên tôi nghĩ đến nơi tôi có thể sử dụng nó là giữ tất cả các tham số mặc định của tôi ở một nơi duy nhất.

public void someMethod( object parm2, ArrayList parm3 )
{ 
  someMethod( null, parm2, parm3 );
}
public void someMethod( string parm1, ArrayList parm3 )
{
  someMethod( parm1, null, parm3 );
}
public void someMethod( string parm1, object parm2, )
{
  someMethod( parm1, parm2, null );
}
public void someMethod( string parm1 )
{
  someMethod( parm1, null, null );
}
public void someMethod( object parm2 )
{
  someMethod( null, parm2, null );
}
public void someMethod( ArrayList parm3 )
{
  someMethod( null, null, parm3 );
}
public void someMethod( string parm1, object parm2, ArrayList parm3 )
{
  // Set your default parameters here rather than scattered through the above function overloads
  parm1 = parm1 ?? "Default User Name";
  parm2 = parm2 ?? GetCurrentUserObj();
  parm3 = parm3 ?? DefaultCustomerList;

  // Do the rest of the stuff here
}

2

Một trường hợp sử dụng kỳ lạ, nhưng tôi đã có một phương thức trong đó một IDisposableđối tượng có khả năng được truyền dưới dạng một đối số (và do đó được xử lý bởi cha mẹ), nhưng cũng có thể là null (và do đó nên được tạo và xử lý trong phương thức cục bộ)

Để sử dụng nó, mã trông giống như

Channel channel;
Authentication authentication;

if (entities == null)
{
    using (entities = Entities.GetEntities())
    {
        channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
        [...]
    }
}
else
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

Nhưng với sự kết hợp null trở nên gọn gàng hơn nhiều

using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

0

Tôi đã sử dụng như thế này:

for (int i = 0; i < result.Count; i++)
            {
                object[] atom = result[i];

                atom[3] = atom[3] ?? 0;
                atom[4] = atom[4] != null ? "Test" : string.Empty;
                atom[5] = atom[5] ?? "";
                atom[6] = atom[6] ?? "";
                atom[7] = atom[7] ?? "";
                atom[8] = atom[8] ?? "";
                atom[9] = atom[9] ?? "";
                atom[10] = atom[10] ?? "";
                atom[12] = atom[12] ?? false; 
            }
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.