Các tham chiếu không thể null C # 8 và mẫu Thử


22

Có một mẫu trong các lớp C # được minh họa bằng Dictionary.TryGetValueint.TryParse: một phương thức trả về một boolean biểu thị sự thành công của một hoạt động và một tham số out chứa kết quả thực tế; nếu hoạt động thất bại, tham số out được đặt thành null.

Giả sử tôi đang sử dụng các tài liệu tham khảo không có giá trị C # 8 và muốn viết một phương thức TryPude cho lớp của riêng tôi. Các đúng chữ ký là thế này:

public static bool TryParse(string s, out MyClass? result);

Vì kết quả là null trong trường hợp sai, biến out phải được đánh dấu là null.

Tuy nhiên, mẫu Thử thường được sử dụng như thế này:

if (MyClass.TryParse(s, out var result))
{
  // use result here
}

Bởi vì tôi chỉ nhập chi nhánh khi hoạt động thành công, kết quả sẽ không bao giờ là null trong chi nhánh đó. Nhưng vì tôi đã đánh dấu nó là nullable nên giờ tôi phải kiểm tra hoặc sử dụng !để ghi đè:

if (MyClass.TryParse(s, out var result))
{
  Console.WriteLine("Look: {0}", result.SomeProperty); // compiler warning, could be null
  Console.WriteLine("Look: {0}", result!.SomeProperty); // need override
}

Điều này là xấu xí và một chút không kinh tế.

Do kiểu sử dụng điển hình, tôi có một tùy chọn khác: nói dối về loại kết quả:

public static bool TryParse(string s, out MyClass result) // not nullable
{
   // Happy path sets result to non-null and returns true.
   // Error path does this:
   result = null!; // override compiler complaint
   return false;
}

Bây giờ cách sử dụng thông thường trở nên đẹp hơn:

if (MyClass.TryParse(s, out var result))
{
  Console.WriteLine("Look: {0}", result.SomeProperty); // no warning
}

nhưng việc sử dụng không điển hình không nhận được cảnh báo nên:

else
{
  Console.WriteLine("Fail: {0}", result.SomeProperty);
  // Yes, result is in scope here. No, it will never be non-null.
  // Yes, it will throw. No, the compiler won't warn about it.
}

Bây giờ tôi không chắc đường nào để đến đây. Có một khuyến nghị chính thức từ nhóm ngôn ngữ C #? Có bất kỳ mã CoreFX nào đã được chuyển đổi thành các tham chiếu không thể rỗng có thể chỉ cho tôi cách thực hiện điều này không? (Tôi đã đi tìmTryParse phương thức. IPAddressLà một lớp có một, nhưng nó chưa được chuyển đổi trên nhánh chính của corefx.)

Và làm thế nào để mã chung như Dictionary.TryGetValueđối phó với điều này? (Có thể với một MaybeNullthuộc tính đặc biệt từ những gì tôi tìm thấy.) Điều gì xảy ra khi tôi khởi tạo a Dictionaryvới loại giá trị không thể rỗng?


Không thử nó (đó là lý do tại sao tôi không viết câu này dưới dạng câu trả lời), nhưng với tính năng khớp mẫu mới của câu lệnh chuyển đổi, tôi đoán một tùy chọn là chỉ trả về tham chiếu nullable (không có mẫu Thử, trở lại MyClass?) và thực hiện một chuyển đổi trên nó với một case MyClass myObj:và (một tùy chọn) case null:.
Filip Milovanović

+1 Tôi thực sự thích câu hỏi này và khi tôi phải làm việc với câu hỏi này, tôi luôn chỉ sử dụng một kiểm tra null bổ sung thay vì ghi đè - điều này luôn cảm thấy hơi không cần thiết và không phù hợp, nhưng nó không bao giờ nằm ​​trong mã hiệu suất quan trọng Vì vậy, tôi chỉ để nó đi. Sẽ rất tốt để xem nếu có một cách xử lý sạch hơn!
BrianH

Câu trả lời:


10

Mẫu bool / out-var không hoạt động tốt với các kiểu tham chiếu nullable, như bạn mô tả. Vì vậy, thay vì chống lại trình biên dịch, hãy sử dụng tính năng này để đơn giản hóa mọi thứ. Sử dụng các tính năng khớp mẫu được cải tiến của C # 8 và bạn có thể coi một tham chiếu không thể là "loại người nghèo có thể":

public static MyClass? TryParse(string s) => 



if (TryParse(someString) is {} myClass)
{
    // myClass wasn't null, we are good to use it
}

Bằng cách đó, bạn tránh làm rối tung outcác tham số và bạn không phải chiến đấu với trình biên dịch khi trộn lẫn nullvới các tham chiếu không thể rỗng.

Và làm thế nào để mã chung như Dictionary.TryGetValueđối phó với điều này?

Tại thời điểm này, "người đàn ông nghèo có thể loại" rơi xuống. Thách thức bạn sẽ gặp phải là khi sử dụng các loại tham chiếu nullable (NRT), trình biên dịch sẽ coi Foo<T>là không nullable. Nhưng hãy thử và thay đổi nó thành Foo<T?>và nó sẽ muốn điều đó Tbị ràng buộc với một lớp hoặc cấu trúc như các loại giá trị nullable là một điều rất khác với quan điểm của CLR. Có nhiều cách giải quyết vấn đề này:

  1. Không bật tính năng NRT,
  2. Bắt đầu sử dụng default(cùng với !) cho outcác tham số mặc dù mã của bạn đang đăng ký không có giá trị null,
  3. Sử dụng một Maybe<T>loại thực làm giá trị trả về, sau đó không bao giờ nullvà bao bọc nó boolout Tvào HasValueValuecác thuộc tính hoặc một số như vậy,
  4. Sử dụng một tuple:
public static (bool success, T result) TryParse<T>(string s) => 


if (TryParse<MyClass>(someString) is (true, var result))
{
    // result is valid here, as success is true
}

Cá nhân tôi thích sử dụng Maybe<T>nhưng có nó hỗ trợ giải cấu trúc để nó có thể được mô hình khớp với dạng như một tuple như trong 4, ở trên.


2
TryParse(someString) is {} myClass- Cú pháp này sẽ mất một số quen thuộc, nhưng tôi thích ý tưởng này.
Sebastian Redl

TryParse(someString) is var myClasscó vẻ dễ dàng hơn với tôi
Olivier Jacot-Descombes

2
@ OlivierJacot-Descombes có thể dễ dàng hơn ... nhưng nó sẽ không hoạt động. Mẫu xe luôn khớp, vì vậy x is var ysẽ luôn đúng, cho dù xcó null hay không.
David Arno

15

Nếu bạn đến đây muộn một chút, như tôi, thì hóa ra nhóm .NET đã xử lý nó thông qua một loạt các thuộc tính tham số như MaybeNullWhen(returnValue: true)trong System.Diagnostics.CodeAnalysiskhông gian mà bạn có thể sử dụng cho mẫu thử.

Ví dụ:

Làm thế nào để mã chung như Dictionary.TryGetValue đối phó với điều này?

bool TryGetValue(TKey key, [MaybeNullWhen(returnValue: false)] out TValue value);

có nghĩa là bạn bị mắng nếu bạn không kiểm tra true

// This is okay:
if(myDictionary.TryGetValue("cheese", out var result))
{
  var more = result * 42;
}

// But this is not:
_ = myDictionary.TryGetValue("cheese", out var result);
var more = result * 42;
// "CS8602: Dereference of a potentially null reference"

Biết thêm chi tiết:


3

Tôi không nghĩ rằng có một cuộc xung đột ở đây.

sự phản đối của bạn đối với

public static bool TryParse(string s, out MyClass? result);

Bởi vì tôi chỉ nhập chi nhánh khi hoạt động thành công, kết quả sẽ không bao giờ là null trong chi nhánh đó.

Tuy nhiên, trên thực tế, không có gì ngăn cản việc gán null cho tham số out trong các hàm TryPude kiểu cũ.

ví dụ.

MyJsonObject.TryParse("null", out obj) //sets obj to a null MyJsonObject and returns true

Cảnh báo đưa ra cho lập trình viên khi họ sử dụng tham số out mà không kiểm tra là chính xác. Bạn nên kiểm tra!

Sẽ có vô số trường hợp bạn sẽ bị buộc phải trả về các kiểu nullable trong đó nhánh chính của mã trả về một kiểu không nullable. Cảnh báo chỉ ở đó để giúp bạn thực hiện những điều này rõ ràng. I E.

MyClass? x = (new List<MyClass>()).FirstOrDefault(i=>i==1);

Cách không mã hóa để mã hóa nó sẽ đưa ra một ngoại lệ trong đó sẽ có một giá trị rỗng. Cho dù phân tích cú pháp, nhận hoặc đầu tiên của bạn

MyClass x = (new List<MyClass>()).First(i=>i==1);

Tôi không nghĩ FirstOrDefaultcó thể so sánh được, bởi vì giá trị trả về của nó tín hiệu chính. Trong các TryParsephương thức, tham số out không null nếu giá trị trả về là true là một phần của hợp đồng phương thức.
Sebastian Redl

nó không phải là một phần của hợp đồng điều duy nhất được đảm bảo là một cái gì đó được gán cho tham số out
Ewan

Đó là hành vi tôi mong đợi từ một TryParsephương pháp. Nếu IPAddress.TryParsebao giờ trả về true nhưng không gán non-null cho tham số out của nó, tôi sẽ báo cáo nó là một lỗi.
Sebastian Redl

Kỳ vọng của bạn là có thể hiểu được nhưng nó không được thực thi bởi trình biên dịch. Vì vậy, chắc chắn, thông số kỹ thuật cho IpAddress có thể nói không bao giờ trả về true và null, nhưng ví dụ JsonObject của tôi cho thấy một trường hợp trả về null có thể đúng
Ewan

"Kỳ vọng của bạn là có thể hiểu được nhưng nó không được thực thi bởi trình biên dịch." - Tôi biết, nhưng câu hỏi của tôi là làm thế nào để viết mã tốt nhất để thể hiện hợp đồng mà tôi có trong đầu, chứ không phải hợp đồng của một số mã khác là gì.
Sebastian Redl
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.