C # 8 การอ้างอิงที่ไม่เป็นโมฆะและรูปแบบลอง


22

มีรูปแบบในคลาส C # เป็นแบบสุดขั้วโดยDictionary.TryGetValueและint.TryParse: วิธีการส่งคืนบูลีนที่ระบุความสำเร็จของการดำเนินการและพารามิเตอร์ out ที่มีผลลัพธ์จริง หากการดำเนินการล้มเหลวพารามิเตอร์ out จะถูกตั้งค่าเป็น null

สมมติว่าฉันใช้การอ้างอิงที่ไม่ใช่โมฆะ C # 8 และต้องการเขียนวิธี TryParse สำหรับคลาสของฉันเอง ถูกต้องลายเซ็นคือ:

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

เนื่องจากผลลัพธ์เป็น null ในกรณีเท็จตัวแปร out ต้องถูกทำเครื่องหมายเป็น nullable

อย่างไรก็ตามโดยทั่วไปจะใช้รูปแบบลองดังนี้:

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

เนื่องจากฉันป้อนสาขาเมื่อการดำเนินการสำเร็จผลลัพธ์ไม่ควรเป็นโมฆะในสาขานั้น แต่เนื่องจากฉันทำเครื่องหมายว่าเป็นโมฆะฉันจึงต้องตรวจสอบสิ่งนั้นหรือใช้!เพื่อแทนที่:

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
}

มันน่าเกลียดและไม่ประหยัด

เนื่องจากรูปแบบการใช้งานทั่วไปฉันมีตัวเลือกอื่น: โกหกเกี่ยวกับประเภทผลลัพธ์:

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;
}

ตอนนี้การใช้งานทั่วไปกลายเป็นดีกว่า:

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

แต่การใช้งานที่ผิดปกติจะไม่ได้รับคำเตือนที่ควรจะ:

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.
}

ตอนนี้ฉันไม่แน่ใจว่าจะไปทางไหน มีคำแนะนำอย่างเป็นทางการจากทีมภาษา C # หรือไม่ มีรหัส CoreFX ใด ๆ ที่แปลงเป็นข้อมูลอ้างอิงที่ไม่สามารถยกเลิกได้ซึ่งแสดงให้ฉันเห็นวิธีการทำเช่นนี้? (ฉันไปหาTryParseวิธีการIPAddressเป็นคลาสที่มี แต่ไม่มีการแปลงในสาขาหลักของ corefx)

และรหัสทั่วไปชอบDictionary.TryGetValueจัดการกับสิ่งนี้อย่างไร (อาจมีMaybeNullคุณลักษณะพิเศษจากสิ่งที่ฉันพบ) จะเกิดอะไรขึ้นเมื่อฉันสร้างอินสแตนซ์ที่Dictionaryมีค่าประเภทไม่เป็นโมฆะ?


ไม่ลอง (ซึ่งเป็นสาเหตุที่ฉันไม่ได้เขียนสิ่งนี้เป็นคำตอบ) แต่ด้วยคุณสมบัติการจับคู่รูปแบบใหม่ของคำสั่งสวิตช์ฉันคาดว่าหนึ่งตัวเลือกคือการคืนค่าการอ้างอิงที่ไม่มีค่าได้เลย (ไม่มีรูปแบบลอง กลับMyClass?) และทำสวิทช์ในนั้นด้วยcase MyClass myObj:และ (เป็น case null:optionall)
Filip Milovanović

+1 ฉันชอบคำถามนี้มากและเมื่อฉันต้องทำงานกับเรื่องนี้ฉันมักจะใช้การตรวจสอบโมฆะพิเศษแทนการแทนที่ - ซึ่งมักจะรู้สึกว่าไม่จำเป็นและไม่จำเป็นเล็กน้อย แต่ก็ไม่เคยมีประสิทธิภาพในการใช้รหัสที่สำคัญ ดังนั้นฉันจะปล่อยมันไป คงจะดีถ้าดูว่ามีวิธีการจัดการที่สะอาดกว่านี้ไหม!
BrianH

คำตอบ:


10

รูปแบบ bool / out-var ใช้งานไม่ได้กับประเภทอ้างอิงแบบ nullable ตามที่คุณอธิบาย ดังนั้นแทนที่จะต่อสู้กับคอมไพเลอร์ใช้คุณลักษณะเพื่อทำให้สิ่งต่าง ๆ ง่ายขึ้น เข้าสู่คุณสมบัติการจับคู่รูปแบบที่ปรับปรุงแล้วของ C # 8 และคุณสามารถใช้การอ้างอิงแบบไม่มีค่าใช้จ่ายในฐานะ "ประเภทคนจนของคนจน":

public static MyClass? TryParse(string s) => 



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

ด้วยวิธีนี้คุณหลีกเลี่ยงการยุ่งกับoutพารามิเตอร์และคุณไม่ต้องต่อสู้กับคอมไพเลอร์มากกว่าการผสมnullกับการอ้างอิงที่ไม่เป็นโมฆะ

และรหัสทั่วไปชอบDictionary.TryGetValueจัดการกับสิ่งนี้อย่างไร

เมื่อมาถึงจุดนี้ว่า "คนจนอาจจะพิมพ์" ตกลง ความท้าทายที่คุณจะเผชิญคือเมื่อใช้ประเภทอ้างอิงที่เป็นโมฆะ (NRT) คอมไพเลอร์จะถือว่าFoo<T>ไม่เป็นโมฆะ แต่ลองและเปลี่ยนเป็นFoo<T?>และมันต้องการให้Tข้อ จำกัด กับคลาสหรือโครงสร้างเนื่องจากชนิดของค่าที่ไม่มีค่าเป็นสิ่งที่แตกต่างจากมุมมองของ CLR มีวิธีแก้ไขที่หลากหลายสำหรับสิ่งนี้:

  1. อย่าเปิดใช้งานคุณสมบัติ NRT
  2. เริ่มใช้default(พร้อม!) สำหรับoutพารามิเตอร์แม้ว่ารหัสของคุณจะสมัครเป็นโมฆะ
  3. ใช้Maybe<T>ชนิดของจริงเป็นค่าส่งคืนซึ่งจะไม่มีnullและล้อมรอบนั้นboolและout Tเข้าไปในHasValueและValueคุณสมบัติหรือบางอย่าง
  4. ใช้ 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
}

โดยส่วนตัวแล้วผมชอบที่จะใช้Maybe<T>แต่ถ้ามีมันรองรับ deconstruct เพื่อให้มันสามารถจับคู่รูปแบบเป็น tuple ได้ใน 4 ด้านบน


2
TryParse(someString) is {} myClass- ไวยากรณ์นี้จะใช้เวลาทำความคุ้นเคย แต่ฉันชอบความคิด
เซบาสเตียนเรดล

TryParse(someString) is var myClassดูง่ายขึ้นสำหรับฉัน
Olivier Jacot-Descombes

2
@ OlivierJacot-Descombes มันอาจดูง่ายกว่า ... แต่มันใช้งานไม่ได้ รูปแบบรถตรงกันเสมอดังนั้นx is var yจะเป็นจริงเสมอไม่ว่าจะxเป็นโมฆะหรือไม่
David Arno

15

หากคุณมาถึงช้ากว่านี้อย่างฉันมันกลับกลายเป็นว่าทีม. NET ได้ส่งผ่านแอททริบิวต์พารามิเตอร์จำนวนมากเช่นMaybeNullWhen(returnValue: true)ในSystem.Diagnostics.CodeAnalysisพื้นที่ซึ่งคุณสามารถใช้สำหรับรูปแบบลองได้

ตัวอย่างเช่น:

รหัสทั่วไปเช่น Dictionary.TryGetValue จัดการกับสิ่งนี้ได้อย่างไร

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

ซึ่งหมายความว่าคุณจะถูกตะโกนถ้าคุณไม่ตรวจสอบ 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"

รายละเอียดเพิ่มเติม:


3

ฉันไม่คิดว่าจะมีความขัดแย้งที่นี่

คุณคัดค้าน

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

คือ

เนื่องจากฉันป้อนสาขาเมื่อการดำเนินการสำเร็จผลลัพธ์ไม่ควรเป็นโมฆะในสาขานั้น

อย่างไรก็ตามในความเป็นจริงไม่มีอะไรขัดขวางการกำหนดค่า Null ให้กับพารามิเตอร์ out ในฟังก์ชัน TryParse สไตล์เก่า

เช่น.

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

คำเตือนให้กับโปรแกรมเมอร์เมื่อพวกเขาใช้พารามิเตอร์ออกโดยไม่ตรวจสอบว่าถูกต้อง คุณควรตรวจสอบ!

จะมีหลายกรณีที่คุณจะถูกบังคับให้ส่งคืนชนิด nullable ที่สาขาหลักของรหัสส่งกลับประเภท non-nullable คำเตือนอยู่ที่นั่นเพื่อช่วยให้คุณทำสิ่งเหล่านี้อย่างชัดเจน กล่าวคือ

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

วิธีที่ไม่เป็นโมฆะในการเขียนโค้ดจะทำให้เกิดข้อยกเว้นซึ่งจะมีผลเป็นโมฆะ ไม่ว่าจะเป็นการแยกวิเคราะห์รับหรือลำดับแรกของคุณ

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

ฉันไม่คิดว่าFirstOrDefaultจะนำไปเปรียบเทียบได้เพราะความว่างเปล่าของค่าส่งคืนเป็นสัญญาณหลัก ในTryParseวิธีการพารามิเตอร์ออกไม่เป็นโมฆะถ้าค่าส่งคืนเป็นจริงเป็นส่วนหนึ่งของสัญญาวิธี
เซบาสเตียนเรดล

มันไม่ได้เป็นส่วนหนึ่งของสัญญา สิ่งเดียวที่มั่นใจได้คือสิ่งที่กำหนดให้กับพารามิเตอร์ out
Ewan

มันเป็นพฤติกรรมที่ฉันคาดหวังจากTryParseวิธีการหนึ่ง หากIPAddress.TryParseเคยส่งคืนจริง แต่ไม่ได้กำหนดพารามิเตอร์ที่ไม่เป็นโมฆะให้กับพารามิเตอร์ out ฉันจะรายงานว่าเป็นข้อบกพร่อง
เซบาสเตียนเรดล

ความคาดหวังของคุณนั้นเข้าใจได้ แต่คอมไพเลอร์ไม่ได้บังคับใช้ ดังนั้นมั่นใจว่าข้อมูลจำเพาะสำหรับ IpAddress อาจจะบอกว่าไม่เคยกลับจริงและ null แต่ JSONObject ตัวอย่างเช่นการแสดงของฉันกรณีที่กลับ null อาจจะถูกต้อง
Ewan

"ความคาดหวังของคุณนั้นเข้าใจได้ แต่คอมไพเลอร์ไม่ได้บังคับใช้" - ฉันรู้ แต่คำถามของฉันคือวิธีเขียนรหัสของฉันให้ดีที่สุดเพื่อแสดงสัญญาที่ฉันมีอยู่ในใจไม่ใช่สัญญาของรหัสอื่นคืออะไร
เซบาสเตียนเรดล
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.