วิธีที่ดีที่สุดในการเขียนวิธี“ ลอง” ใน C # 7 คืออะไร


21

ฉันกำลังเขียนการใช้ Queue ประเภทที่มีTryDequeueวิธีการที่ใช้รูปแบบคล้ายกับTryParseวิธีการ. NET ต่าง ๆซึ่งฉันส่งคืนค่าบูลีนหากการดำเนินการสำเร็จและใช้outพารามิเตอร์เพื่อส่งคืนค่าที่ถูกตัดจริง

public bool TryDequeue(out Message message) => _innerQueue.TryDequeue(out message);

ตอนนี้ฉันชอบหลีกเลี่ยงoutparams ทุกครั้งที่ทำได้ C # 7 ทำให้เรามีตัวแปรต่าง ๆ เพื่อให้การทำงานกับมันง่ายขึ้น แต่ฉันยังคงพิจารณาถึงความชั่วร้ายที่จำเป็นมากกว่าเครื่องมือที่มีประโยชน์

พฤติกรรมที่ฉันต้องการจากวิธีนี้มีดังนี้:

  • หากมีรายการที่จะ dequeue คืนมัน
  • หากไม่มีรายการที่จะถอน (คิวว่างเปล่าให้ผู้โทรมีข้อมูลเพียงพอที่จะดำเนินการอย่างเหมาะสม
  • อย่าเพิ่งส่งคืนรายการที่ไม่มีค่าหากไม่มีรายการที่เหลืออยู่
  • อย่าโยนข้อยกเว้นหากพยายาม dequeue จากคิวที่ว่างเปล่า

ตอนนี้ผู้เรียกใช้เมธอดนี้มักจะใช้รูปแบบดังต่อไปนี้ (โดยใช้ไวยากรณ์ตัวแปร C # 7 out):

if (myMessageQueue.TryDequeue(out Message dequeued))
    MyMessagingClass.SendMessage(dequeued)
else
    Console.WriteLine("No messages!"); // do other stuff

ซึ่งไม่เลวร้ายที่สุดทุกคนบอก แต่ฉันอดไม่ได้ที่จะรู้สึกว่าอาจจะมีวิธีที่ดีกว่าในการทำสิ่งนี้ (ฉันยินดีอย่างยิ่งที่จะยอมรับว่าอาจไม่มี) ฉันเกลียดที่ผู้โทรต้องแยกออกมันเป็นแบบมีเงื่อนไขเมื่อทุกสิ่งที่มันต้องการคือการรับค่าถ้ามีอยู่

รูปแบบอื่น ๆ ที่มีอยู่เพื่อให้บรรลุพฤติกรรม "ลอง" แบบเดียวกันนี้มีอะไรบ้าง

สำหรับบริบทวิธีนี้อาจเรียกได้ว่าในโครงการ VB ดังนั้นคะแนนโบนัสสำหรับสิ่งที่ใช้ได้ดีทั้งสองอย่าง ความจริงเรื่องนี้ควรมีน้ำหนักน้อยมาก


9
กำหนดโครงสร้างOption<T>และส่งคืน bool Try(..., out data)ฟังก์ชั่นเหล่านั้นเป็นสิ่งที่น่ารังเกียจ
CodesInChaos

2
ฉันกำลังคิดคล้ายกัน ... บางที <T> ตัวเลือก <T> หากมีพารามิเตอร์ที่ไม่พึงประสงค์ OUT
Jon Raynor

1
@FrustratedWithFormsDesigner ดีฉันยังไม่ได้ "ลอง" สิ่งอื่น ๆ อีกมาก (ยินดีต้อนรับเล่นสำนวน) มากเท่าที่พวกเขาคิด ฉันมีความคิดที่จะส่งคืน ValueTuple แต่ที่ดีที่สุดฉันไม่คิดว่าจะมีการปรับปรุงมากนัก
Eric Sondergard

@CodesInChaos เราอยู่ในหน้าเดียวกันนั่นเป็นเหตุผลที่ฉันมาที่นี่! และฉันชอบความคิดนี้ หากคุณมีเวลาคุณจะให้รายละเอียดเพิ่มเติมในคำตอบเพื่อที่ฉันจะได้ยอมรับหรือไม่
Eric Sondergard

คำตอบ:


24

ใช้ตัวเลือกประเภทซึ่งจะบอกว่าวัตถุประเภทที่มีสองรุ่นโดยทั่วไปเรียกว่า "บาง" (เมื่อมีค่า) หรือ "ไม่มี" (เมื่อไม่มีค่า) ... หรือบางครั้ง พวกมันถูกเรียกว่า Just and Nothing จากนั้นมีฟังก์ชั่นในประเภทเหล่านี้ที่ให้คุณเข้าถึงค่าหากมีอยู่ทดสอบว่ามีอยู่และที่สำคัญที่สุดคือวิธีการที่ให้คุณใช้ฟังก์ชั่นที่คืนค่าตัวเลือกเพิ่มเติมให้กับค่าหากมีอยู่ (โดยทั่วไปใน C # จะเรียกว่า FlatMap แม้ว่าในภาษาอื่น ๆ มักจะถูกเรียกว่า Bind แทน ... ชื่อมีความสำคัญใน C # เนื่องจากมีเมธอด s ของชื่อนี้และประเภทให้คุณใช้อ็อบเจกต์ Option ของคุณในคำสั่ง LINQ)

คุณสมบัติเพิ่มเติมอาจรวมถึงวิธีการเช่น IfPresent และ IfNotPresent เพื่อเรียกใช้การกระทำในเงื่อนไขที่เกี่ยวข้องและ OrElse (ซึ่งจะแทนที่ค่าเริ่มต้นเมื่อไม่มีค่าอยู่ แต่ไม่มี op อื่น) และอื่น ๆ

ตัวอย่างของคุณอาจมีลักษณะดังนี้:

myMessageQueue.TryDeque()
    .IfPresent( dequeued => MyMessagingClass.SendMessage(dequeued))
    .IfNotPresent (() =>  Console.WriteLine("No messages!")

นี่คือรูปแบบตัวเลือก (หรืออาจ) monad และมันมีประโยชน์มาก มีการนำไปใช้งานที่มีอยู่แล้ว (เช่น https://github.com/nlkl/Optional/blob/master/README.md ) แต่ก็ไม่ยากสำหรับตัวคุณเอง

(คุณอาจต้องการขยายรูปแบบนี้เพื่อให้คุณส่งคืนคำอธิบายของข้อผิดพลาดสาเหตุแทนสิ่งใดเมื่อวิธีการล้มเหลว ... นี่เป็นไปได้ทั้งหมดและมักจะเรียกว่า Either monad เป็นชื่อที่แสดงถึงคุณสามารถใช้ FlatMap เดียวกัน รูปแบบเพื่อให้ง่ายต่อการทำงานในกรณีนั้นด้วย)


นี่เป็นตัวเลือกที่ดีแน่นอนขอบคุณสำหรับคำแนะนำและความคิดของคุณ ฉันกำลังค้นหาความคิดเพิ่มเติมที่นี่จริง ๆ
Eric Sondergard

2
สิ่งที่ดีเกี่ยวกับOption/ Maybeคือมันเป็น monad (ถ้าดำเนินการเป็นหนึ่งแน่นอน) ซึ่งหมายความว่ามันสามารถผูกมัดปลอดภัยห่อห่อและแปรรูปโดยไม่จำเป็นต้องจัดการกับกรณีที่แตกต่างกันโดยตรงและการผูกมัดนี้และ การประมวลผลสามารถทำได้ด้วย LINQ Query Expressions
Jörg W Mittag

3
โดยวิธีการflatMap/ bindเรียกว่าSelectManyใน. NET
Jörg W Mittag

5
ฉันสงสัยว่ามันจะเป็นความคิดที่ดีกว่าถ้าเลือกชื่ออื่นเนื่องจากการทำเช่นนี้คุณกำลังทำผิดสัญญาTry...การตั้งชื่อใน. NET (และอาจทำให้ผู้ดูแลระบบสับสน)
Bob

@ Bob นั่นเป็นจุดที่ดี ฉันเห็นด้วย.
Eric Sondergard

12

คำตอบที่ได้รับนั้นดีและฉันจะไปกับหนึ่งในนั้น ลองพิจารณาคำตอบนี้เพื่อเติมความคิดใหม่ ๆ เข้าไปในมุมต่าง ๆ :

  • subclasses ทำให้ของMessageที่เรียกว่าและSomeMessage ตอนนี้สามารถกลับมาได้หากไม่มีข้อความและถ้ามีข้อความ หากผู้รับใช้สนใจที่จะตรวจจับกรณีที่พวกเขาอยู่พวกเขาสามารถทำได้อย่างง่ายดายโดยการตรวจสอบประเภท ถ้าพวกเขาทำไม่ได้ก็อย่าทำอะไรเลยเมื่อใดก็ตามที่วิธีการใด ๆ ถูกเรียกใช้และเฮ้พวกเขาได้รับสิ่งที่พวกเขาร้องขอNoMessageDequeueNoMessageSomeMessageNoMessage

  • เช่นเดียวกับข้างต้น แต่ทำให้Messageเปลี่ยนแปลงได้โดยปริยายเพื่อbool(หรือนำไปใช้operator true / operator false) ตอนนี้คุณสามารถพูดif (message)และให้มันเป็นจริงถ้าข้อความเป็นสิ่งที่ดีและผิดถ้ามันไม่ดี (นี่เป็นความคิดที่ไม่ดี แต่ฉันรวมไว้เพื่อความสมบูรณ์!)

  • C # 7 มีสิ่งอันดับ คืนค่า(bool, Message)tuple

  • ทำให้Messageประเภท struct Message?และกลับ


เฮ้ขอบคุณสำหรับคำตอบของคุณ ด้วยคำถามนี้ฉันพยายามเปิดเผยตัวเองกับแนวคิดที่แตกต่างกันมากพอ ๆ กับที่ฉันพยายามหาวิธีแก้ไขปัญหาเฉพาะของฉัน ไชโย!
Eric Sondergard

ฉันหมายความว่าถ้า "ความคิดที่ไม่ดี" ของคุณถูกใช้โดย Unity ทำไมเราถึงใช้มันไม่ได้?
Arturo Torres Sánchez

@ ArturoTorresSánchez: คำถามของคุณคือ "มีคนอื่นใช้การเขียนโปรแกรมที่แย่มากดังนั้นทำไมฉันถึงทำไม่ได้?" คำถามนั้นไม่ต่อเนื่องกัน ไม่มีใครหยุดคุณจากการใช้วิธีการเขียนโปรแกรมที่ไม่ดีเหมือนคนอื่น คุณไปข้างหน้า นั่นจะไม่ทำให้เป็นแนวปฏิบัติที่ดีใน C #
Eric Lippert

มันเป็นแก้มลิ้น ฉันเดาว่าฉันต้องใช้ "/ s" เพื่อทำเครื่องหมาย
Arturo Torres Sánchez

@ ArturoTorresSánchez: นี่คือคำถามและคำตอบไซต์; ผมคิดว่าคำถามจะเป็นคำถามที่กำลังมองหาคำตอบ
Eric Lippert

10

ใน C # 7 คุณสามารถใช้การจับคู่รูปแบบเพื่อให้ได้ผลลัพธ์ที่เหมือนกันในวิธีที่หรูหรากว่า:

if (myMessageQueue.TryDequeue() is Message dequeued) 
{
     MyMessagingClass.SendMessage(dequeued)
} 
else 
{
    Console.WriteLine("No messages!"); // do other stuff
}

ในกรณีพิเศษนี้ฉันอาจจะใช้เหตุการณ์แทนการสำรวจคิวซ้ำ ๆ


3
แม้ว่าจะไม่ต้องการTryDequeueส่งคืนอินเทอร์เฟซของตัวทำเครื่องหมายด้วยการMessageใช้งานและการใช้งาน "ไม่มีอะไร" บ้างหรือไม่ แน่นอนว่ามันมีความหรูหรามากขึ้นในไซต์การโทร แต่คุณติดตั้งการใช้งาน 3 ครั้งในรหัสจริงซึ่งอาจเป็นไปไม่ได้หากMessageเป็นประเภทที่มีอยู่
Telastyn

1
@Telastyn: นอกจากนี้ยังทำงานถ้ามันเป็นเพียงแค่ส่งกลับnull คุณสามารถใช้ประเภทตัวเลือก
JacquesB

@JacquesB: แต่ถ้าเป็นโมฆะเป็นรายการที่สามารถจัดคิวได้ถูกต้อง
JBSnorro

5

ไม่มีอะไรผิดปกติกับวิธีลอง ... (มีพารามิเตอร์ out) สำหรับสถานการณ์ที่ความล้มเหลว (ไม่มีค่า) เป็นเรื่องธรรมดาพอ ๆ กับความสำเร็จ

แต่ถ้าคุณยืนยันที่จะทำสิ่งที่แตกต่างคุณอาจต้องการส่งข้อความว่างเปล่าและเพียงแค่ส่งข้อความนั้น (ไม่ใช่ปัญหาของคุณอีกต่อไป) หรือเลื่อนคำสั่ง if จนกว่าคุณจะต้องรู้จริง ๆ ว่าคุณมีข้อความที่มีเนื้อหาหรือไม่

โปรดทราบว่าบางคนต้องทำแบบทดสอบในบางครั้ง ฉันจะโต้แย้งการทดสอบควรทำเมื่อใดและที่คำถามเป็นปัจจุบัน นี่คือเวลาที่ dequeing


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