ส่งคืนค่าสองค่า Tuple vs 'out' vs 'struct'


88

พิจารณาฟังก์ชันที่คืนค่าสองค่า เราสามารถเขียน:

// Using out:
string MyFunction(string input, out int count)

// Using Tuple class:
Tuple<string, int> MyFunction(string input)

// Using struct:
MyStruct MyFunction(string input)

ข้อใดเป็นแนวทางปฏิบัติที่ดีที่สุดและเพราะเหตุใด


สตริงไม่ใช่ประเภทค่า ฉันคิดว่าคุณควรพูดว่า "พิจารณาฟังก์ชันที่คืนค่าสองค่า"
Eric Lippert

@ เอริก: คุณพูดถูก ฉันหมายถึงประเภทที่ไม่เปลี่ยนรูป
Xaqron

แล้วชั้นเรียนผิดอะไร?
Lukasz Madon

1
@lukas: ไม่มีอะไร แต่แน่นอนว่ามันไม่ได้อยู่ในแนวทางปฏิบัติที่ดีที่สุด นี่เป็นค่าที่เบา (<16 KB) และถ้าฉันจะเพิ่มโค้ดที่กำหนดเองฉันจะใช้structตามที่Ericกล่าวไว้
Xaqron

1
ฉันจะบอกว่าจะใช้เฉพาะเมื่อคุณต้องการค่าส่งคืนเพื่อตัดสินใจว่าคุณควรประมวลผลข้อมูลที่ส่งคืนหรือไม่เช่นเดียวกับใน TryParse มิฉะนั้นคุณควรส่งคืนวัตถุที่มีโครงสร้างเสมอเช่นว่าวัตถุที่มีโครงสร้างควรเป็นประเภทค่าหรือข้อมูลอ้างอิง ประเภทขึ้นอยู่กับว่าคุณใช้ข้อมูลอะไรเพิ่มเติม
MikeT

คำตอบ:


94

แต่ละคนมีข้อดีข้อเสีย

พารามิเตอร์ออกนั้นรวดเร็วและราคาถูก แต่คุณต้องส่งผ่านตัวแปรและอาศัยการกลายพันธุ์ แทบจะเป็นไปไม่ได้เลยที่จะใช้พารามิเตอร์ out กับ LINQ อย่างถูกต้อง

Tuples สร้างแรงกดดันในการเก็บขยะและไม่ได้จัดทำเอกสาร "Item1" ไม่ต้องบรรยายมาก

โครงสร้างแบบกำหนดเองอาจทำสำเนาได้ช้าหากมีขนาดใหญ่ แต่มีการจัดทำเอกสารด้วยตนเองและมีประสิทธิภาพหากมีขนาดเล็ก อย่างไรก็ตามมันเป็นความเจ็บปวดเช่นกันที่จะกำหนดโครงสร้างที่กำหนดเองทั้งหมดสำหรับการใช้งานเล็กน้อย

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

อัปเดต: โปรดทราบว่าสิ่งที่เพิ่มขึ้นใน C # 7 ซึ่งส่งมาหกปีหลังจากเขียนบทความนี้เป็นประเภทมูลค่าและมีโอกาสน้อยที่จะสร้างแรงกดดันในการรวบรวม


2
การส่งคืนค่าสองค่ามักใช้แทนการไม่มีชนิดตัวเลือกหรือ ADT
Anton Tykhyy

2
จากประสบการณ์ของฉันกับภาษาอื่นฉันจะบอกว่าโดยทั่วไปแล้วสิ่งที่ถูกใช้เพื่อการจัดกลุ่มรายการที่รวดเร็วและสกปรก โดยปกติจะเป็นการดีกว่าที่จะสร้างคลาสหรือโครงสร้างเพียงเพราะช่วยให้คุณสามารถตั้งชื่อแต่ละรายการได้ เมื่อใช้ tuples ความหมายของแต่ละค่าอาจเป็นเรื่องยากที่จะระบุ แต่จะช่วยให้คุณไม่ต้องเสียเวลาในการสร้างคลาส / โครงสร้างซึ่งอาจมากเกินไปหากไม่ใช้คลาส / โครงสร้างดังกล่าวที่อื่น
Kevin Cathcart

23
@Xaqron: หากคุณพบว่าแนวคิดเรื่อง "data with a timeout" เป็นเรื่องธรรมดาในโปรแกรมของคุณคุณอาจพิจารณาสร้างประเภททั่วไป "TimeLimited <T>" เพื่อให้วิธีการของคุณส่งคืน TimeLimited <string> หรือ TimeLimited <Uri> หรืออะไรก็ตาม จากนั้นคลาส TimeLimited <T> สามารถมีตัวช่วยที่บอกคุณว่า "เราเหลือเวลาอีกนานแค่ไหน" หรือ "หมดอายุหรือยัง" หรืออะไรก็ตาม พยายามจับความหมายที่น่าสนใจเช่นนี้ในระบบประเภท
Eric Lippert

3
แน่นอนฉันจะไม่ใช้ Tuple เป็นส่วนหนึ่งของอินเทอร์เฟซสาธารณะ แต่ถึงแม้จะเป็นรหัส "ส่วนตัว" ฉันก็สามารถอ่านได้อย่างมากจากประเภทที่เหมาะสมแทนที่จะใช้ Tuple (โดยเฉพาะอย่างยิ่งการสร้างประเภทภายในส่วนตัวด้วยคุณสมบัติอัตโนมัตินั้นง่ายเพียงใด)
SolutionYogi

2
แรงกดดันในการสะสมหมายถึงอะไร?
ม้วน

27

การเพิ่มคำตอบก่อนหน้านี้ C # 7 จะนำสิ่งที่เป็นค่าประเภทสิ่งที่ไม่เหมือน System.Tupleที่เป็นประเภทอ้างอิงและยังนำเสนอความหมายที่ปรับปรุง

คุณยังคงสามารถปล่อยให้ไม่มีชื่อและใช้.Item*ไวยากรณ์ได้:

(string, string, int) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.Item1; //John
person.Item2; //Doe
person.Item3;   //42

แต่สิ่งที่มีประสิทธิภาพมากเกี่ยวกับคุณลักษณะใหม่นี้คือความสามารถในการตั้งชื่อทูเปิล ดังนั้นเราจึงสามารถเขียนด้านบนใหม่ได้ดังนี้:

(string FirstName, string LastName, int Age) getPerson()
{
    return ("John", "Doe", 42);
}

var person = getPerson();
person.FirstName; //John
person.LastName; //Doe
person.Age;   //42

ยังรองรับการทำลายล้าง:

(string firstName, string lastName, int age) = getPerson()


2
ฉันคิดถูกต้องหรือไม่ที่จะส่งคืนโครงสร้างที่มีการอ้างอิงเป็นสมาชิกภายใต้ประทุน
Austin_Anderson

4
เรารู้หรือไม่ว่าประสิทธิภาพของสิ่งนั้นเมื่อเทียบกับการใช้พารามิเตอร์ภายนอกเป็นอย่างไร
SpaceMonkey

20

ฉันคิดว่าคำตอบขึ้นอยู่กับความหมายของสิ่งที่ฟังก์ชันกำลังทำอยู่และความสัมพันธ์ระหว่างค่าทั้งสอง

ตัวอย่างเช่นTryParseเมธอดใช้outพารามิเตอร์เพื่อยอมรับค่าที่แยกวิเคราะห์และส่งกลับ a boolเพื่อระบุว่าการแยกวิเคราะห์สำเร็จหรือไม่ ทั้งสองค่าไม่ได้อยู่ด้วยกันจริงๆดังนั้นในทางความหมายมันจึงสมเหตุสมผลกว่าและจุดประสงค์ของรหัสนั้นอ่านง่ายกว่าใช้outพารามิเตอร์

อย่างไรก็ตามหากฟังก์ชั่นของคุณส่งคืนพิกัด X / Y ของวัตถุบางอย่างบนหน้าจอแสดงว่าทั้งสองค่าอยู่ด้วยกันตามความหมายและจะเป็นการดีกว่าถ้าใช้ a struct .

โดยส่วนตัวแล้วฉันจะหลีกเลี่ยงการใช้ a tupleสำหรับทุกสิ่งที่โค้ดภายนอกจะมองเห็นได้เนื่องจากไวยากรณ์ที่น่าอึดอัดในการดึงข้อมูลสมาชิก


+1 สำหรับความหมาย คำตอบของคุณคือประเภทอ้างอิงเล็กน้อยที่เหมาะสมกว่าเมื่อเราสามารถออกจากoutพารามิเตอร์nullได้ มีไม่กี่ประเภทที่ไม่เปลี่ยนรูปเป็นโมฆะอยู่ที่นั่น
Xaqron

3
ที่จริงแล้วทั้งสองค่าใน TryParse นั้นอยู่ร่วมกันเป็นอย่างมากโดยนัยว่ามีค่าหนึ่งเป็นค่าส่งคืนและอีกค่าหนึ่งเป็นพารามิเตอร์ ByRef ในหลาย ๆ ทางตรรกะที่จะส่งคืนจะเป็นประเภทที่ว่างเปล่า มีบางกรณีที่รูปแบบ TryParse ทำงานได้ดีและบางกรณีก็เจ็บปวด (เป็นเรื่องดีที่สามารถใช้ในคำสั่ง "if" ได้ แต่มีหลายกรณีที่ส่งคืนค่าที่เป็นโมฆะหรือสามารถระบุค่าเริ่มต้นได้ จะสะดวกกว่า).
supercat

@supercat ฉันเห็นด้วยกับแอนดรูว์พวกเขาไม่ได้อยู่ด้วยกัน แม้ว่าพวกเขาจะเกี่ยวข้องกัน แต่ผลตอบแทนจะบอกคุณว่าคุณจำเป็นต้องกังวลกับมูลค่าหรือไม่ไม่ใช่สิ่งที่ต้องประมวลผลควบคู่ ดังนั้นหลังจากที่คุณประมวลผลการส่งคืนแล้วจึงไม่จำเป็นสำหรับการประมวลผลอื่น ๆ ที่เกี่ยวข้องกับค่า out อีกต่อไปสิ่งนี้จะแตกต่างจากการบอกว่าส่งคืน KeyValuePair จากพจนานุกรมที่มีการเชื่อมโยงระหว่างคีย์และค่าที่ชัดเจนและกำลังดำเนินการอยู่ แม้ว่าฉันจะเห็นด้วยหากประเภทที่เป็นโมฆะได้อยู่ใน. Net 1.1 พวกเขาอาจจะใช้มันเป็นโมฆะก็เป็นวิธีที่เหมาะสมในการตั้งค่าสถานะว่าไม่มีค่า
MikeT

@ MikeT: ฉันคิดว่าเป็นเรื่องที่โชคร้ายเป็นอย่างยิ่งที่ Microsoft ได้แนะนำว่าควรใช้โครงสร้างสำหรับสิ่งที่เป็นตัวแทนของค่าเดียวเท่านั้นเมื่อในความเป็นจริงโครงสร้างฟิลด์ที่เปิดเผยเป็นสื่อที่เหมาะสำหรับการส่งผ่านกลุ่มของตัวแปรอิสระที่ผูกเข้าด้วยกันด้วยเทปพันสายไฟ . ตัวบ่งชี้ความสำเร็จและมูลค่ามีความหมายร่วมกันในขณะที่ส่งคืนแม้ว่าหลังจากนั้นจะมีประโยชน์มากกว่าในฐานะตัวแปรแยกกัน ฟิลด์ของโครงสร้างฟิลด์สัมผัสที่จัดเก็บในตัวแปรนั้นสามารถใช้งานได้เป็นตัวแปรแยกต่างหาก ไม่ว่าในกรณีใด ๆ ...
supercat

@MikeT: เนื่องจากความแปรปรวนร่วมและไม่ได้รับการสนับสนุนภายในเฟรมเวิร์กtryรูปแบบเดียวที่ใช้ได้กับอินเทอร์เฟซที่มีความแปรปรวนร่วมกันคือT TryGetValue(whatever, out bool success); วิธีการนั้นจะอนุญาตให้มีอินเทอร์เฟซIReadableMap<in TKey, out TValue> : IReadableMap<out TValue>และปล่อยให้โค้ดที่ต้องการแมปอินสแตนซ์ของAnimalอินสแตนซ์Carเพื่อยอมรับDictionary<Cat, ToyotaCar>[using TryGetValue<TKey>(TKey key, out bool success). ไม่มีความแปรปรวนดังกล่าวเป็นไปได้หากTValueใช้เป็นrefพารามิเตอร์
supercat

2

ฉันจะใช้วิธีการใช้พารามิเตอร์ Out เพราะในแนวทางที่สองคุณจะต้องสร้างและวัตถุของคลาส Tuple จากนั้นเพิ่มมูลค่าให้ซึ่งฉันคิดว่าเป็นการดำเนินการที่มีค่าใช้จ่ายสูงเมื่อเทียบกับการคืนค่าพารามิเตอร์ in out แม้ว่าคุณต้องการส่งคืนค่าหลายค่าใน Tuple Class (ซึ่ง infact ไม่สามารถทำได้โดยเพียงแค่ส่งกลับพารามิเตอร์เดียว) ฉันจะไปแนวทางที่สอง


outผมเห็นด้วยกับ นอกจากนี้ยังมีparamsคำหลักที่ฉันไม่ได้กล่าวถึงเพื่อให้คำถามตรงไปตรงมา
Xaqron

2

คุณไม่ได้พูดถึงอีกหนึ่งตัวเลือกซึ่งมีคลาสที่กำหนดเองแทนโครงสร้าง หากข้อมูลมีความหมายที่เกี่ยวข้องซึ่งสามารถดำเนินการได้โดยฟังก์ชันหรือขนาดอินสแตนซ์ใหญ่พอ (> 16 ไบต์ตามกฎทั่วไป) อาจต้องการคลาสที่กำหนดเอง ไม่แนะนำให้ใช้ "out" ใน API สาธารณะเนื่องจากการเชื่อมโยงกับพอยน์เตอร์และต้องการความเข้าใจว่าประเภทการอ้างอิงทำงานอย่างไร

https://msdn.microsoft.com/en-us/library/ms182131.aspx

Tuple เหมาะสำหรับการใช้งานภายใน แต่การใช้งานใน API สาธารณะนั้นไม่สะดวก ดังนั้นการโหวตของฉันจึงอยู่ระหว่างโครงสร้างและคลาสสำหรับ API สาธารณะ


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

1

ไม่มี "แนวทางปฏิบัติที่ดีที่สุด" เป็นสิ่งที่คุณพอใจและสิ่งที่ดีที่สุดในสถานการณ์ของคุณ ตราบใดที่คุณปฏิบัติตามนี้ก็ไม่มีปัญหากับแนวทางแก้ไขใด ๆ ที่คุณโพสต์ไว้


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