ความแตกต่างระหว่างความแปรปรวนร่วมและความแปรปรวน


148

ฉันมีปัญหาในการทำความเข้าใจความแตกต่างระหว่างความแปรปรวนร่วมและความแตกต่าง

คำตอบ:


266

คำถามคือ "ความแปรปรวนร่วมและความแตกต่างคืออะไร"

ความแปรปรวนและ contravariance เป็นทรัพย์สินของฟังก์ชั่นการทำแผนที่ที่ร่วมงานหนึ่งในสมาชิกของชุดอีกด้วย โดยเฉพาะอย่างยิ่งการทำแผนที่สามารถแปรปรวนร่วมหรือเทียบเคียงกับความสัมพันธ์ในชุดนั้น

พิจารณาเซตย่อยสองชุดต่อไปนี้ของชุดทุกประเภท C # ครั้งแรก:

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.

และประการที่สองชุดนี้เกี่ยวข้องอย่างชัดเจน:

{ IEnumerable<Animal>, 
  IEnumerable<Tiger>, 
  IEnumerable<Fruit>, 
  IEnumerable<Banana> }

มีการดำเนินการแมปจากชุดแรกเป็นชุดที่สอง นั่นคือสำหรับแต่ละ T ในชุดแรกที่สอดคล้องกันIEnumerable<T>ชนิดในชุดที่สองคือ T → IE<T>หรือในรูปแบบระยะสั้นการทำแผนที่เป็น ขอให้สังเกตว่านี่คือ "ลูกศรบาง"

กับฉันจนถึงตอนนี้

ลองพิจารณาความสัมพันธ์กัน มีความสัมพันธ์ความเข้ากันได้ที่ได้รับมอบหมายระหว่างคู่ประเภทในชุดแรกคือ ค่าประเภทTigerสามารถกำหนดให้กับตัวแปรประเภทAnimalดังนั้นประเภทเหล่านี้จะกล่าวว่า "เข้ากันได้กับการกำหนด" ลองเขียน "ค่าประเภทXสามารถกำหนดให้กับตัวแปรประเภทY" ในรูปแบบที่สั้นกว่า:X ⇒ Yในรูปแบบสั้น: ขอให้สังเกตว่านี่คือ "ลูกศรอ้วน"

ดังนั้นในชุดย่อยแรกของเรานี่คือความสัมพันธ์ที่เข้ากันได้ของการกำหนด:

Tiger   Tiger
Tiger   Animal
Animal  Animal
Banana  Banana
Banana  Fruit
Fruit   Fruit

ใน C # 4 ซึ่งรองรับความเข้ากันได้ของการกำหนด covariant ของอินเทอร์เฟซบางอย่างมีความสัมพันธ์ความเข้ากันได้ที่ได้รับมอบหมายระหว่างคู่ของประเภทในชุดที่สอง:

IE<Tiger>   IE<Tiger>
IE<Tiger>   IE<Animal>
IE<Animal>  IE<Animal>
IE<Banana>  IE<Banana>
IE<Banana>  IE<Fruit>
IE<Fruit>   IE<Fruit>

ขอให้สังเกตว่าการทำแผนที่การดำรงไว้ซึ่งการดำรงอยู่และทิศทางของการทำงานร่วมกันที่ได้รับมอบหมายT → IE<T> นั่นคือถ้าหากX ⇒ Yมันเป็นจริงเช่นกันIE<X> ⇒ IE<Y>เช่นกัน

หากเรามีสองสิ่งที่ด้านใดด้านหนึ่งของลูกศรอ้วนเราสามารถแทนที่ทั้งสองด้านด้วยสิ่งที่อยู่ทางด้านขวามือของลูกศรบางที่สอดคล้องกัน

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

นั่นคือความแปรปรวนร่วม ตอนนี้ให้พิจารณาเซตย่อยของชุดทุกประเภท:

{ IComparable<Tiger>, 
  IComparable<Animal>, 
  IComparable<Fruit>, 
  IComparable<Banana> }

T → IC<T>ตอนนี้เรามีการทำแผนที่จากชุดแรกกับชุดที่สาม

ใน C # 4:

IC<Tiger>   IC<Tiger>
IC<Animal>  IC<Tiger>     Backwards!
IC<Animal>  IC<Animal>
IC<Banana>  IC<Banana>
IC<Fruit>   IC<Banana>     Backwards!
IC<Fruit>   IC<Fruit>

นั่นคือการแมปT → IC<T>ได้รักษาการดำรงอยู่ แต่กลับทิศทางของความเข้ากันได้ที่ได้รับมอบหมาย นั่นคือถ้าX ⇒ Yแล้วIC<X> ⇐ IC<Y>แล้ว

การทำแผนที่ที่เก็บรักษา แต่กลับความสัมพันธ์ที่เรียกว่าการทำแผนที่contravariant

อีกครั้งนี้ควรถูกต้องชัดเจน อุปกรณ์ที่สามารถเปรียบเทียบสัตว์สองตัวยังสามารถเปรียบเทียบเสือสองตัวได้ แต่อุปกรณ์ที่สามารถเปรียบเทียบเสือสองตัวนั้นไม่สามารถเปรียบเทียบสัตว์สองตัวใด ๆ ได้

นั่นคือความแตกต่างระหว่างความแปรปรวนร่วมและความแปรปรวนใน C # 4 ความแปรปรวนร่วมรักษาทิศทางของการมอบหมาย ความแปรปรวนตรงกันข้ามมัน


4
สำหรับคนอย่างฉันจะเป็นการดีกว่าถ้าคุณเพิ่มตัวอย่างที่แสดงว่าอะไรคือความแปรปรวนร่วมและอะไรที่ไม่แปรปรวนและอะไรที่ไม่ใช่ทั้งคู่
bjan

2
@Bargitta: มันคล้ายกันมาก แตกต่างก็คือ C # ใช้แปรปรวนเว็บไซต์ที่ชัดเจนและ Java ใช้แปรปรวนเว็บไซต์โทร ดังนั้นวิธีที่สิ่งต่างกันจะเหมือนกัน แต่ที่นักพัฒนาบอกว่า "ฉันต้องการสิ่งนี้ให้แตกต่าง" อนึ่งคุณลักษณะในทั้งสองภาษาได้รับการออกแบบโดยบุคคลเดียวกัน!
Eric Lippert

2
@AshishNegi: อ่านลูกศรว่า "อาจจะใช้เป็น" "สิ่งที่สามารถเปรียบเทียบสัตว์อาจถูกใช้เป็นสิ่งที่สามารถเปรียบเทียบเสือ" ทำให้รู้สึกตอนนี้หรือไม่
Eric Lippert

1
@AshishNegi: ไม่ไม่ถูกต้อง IEnumerable คือ covariant เนื่องจาก T ปรากฏขึ้นในการส่งคืนเมธอดของ IEnumerable เท่านั้น และ IComparable นั้นมีความแตกต่างกันเนื่องจากT ปรากฏเป็นพารามิเตอร์ที่เป็นทางการของวิธีการของ IComparableเท่านั้น
Eric Lippert

2
@AshishNegi: คุณต้องการคิดถึงเหตุผลเชิงตรรกะที่อยู่ภายใต้ความสัมพันธ์เหล่านี้ ทำไมเราIEnumerable<Tiger>ถึงเปลี่ยนเป็นIEnumerable<Animal>ปลอดภัย? เพราะไม่มีทางที่จะป้อนข้อมูลIEnumerable<Animal>ยีราฟลง ทำไมเราสามารถแปลงIComparable<Animal>ไปIComparable<Tiger>? เพราะไม่มีทางที่จะไม่นำออกIComparable<Animal>ยีราฟจาก ทำให้รู้สึก?
Eric Lippert

111

อาจเป็นตัวอย่างที่ง่ายที่สุด - นั่นเป็นวิธีที่ฉันจำได้ง่าย

แปรปรวน

ตัวอย่างที่ยอมรับได้: IEnumerable<out T>,Func<out T>

คุณสามารถแปลงจากIEnumerable<string>ไปIEnumerable<object>หรือเพื่อFunc<string> Func<object>ค่าจะออกมาจากวัตถุเหล่านี้เท่านั้น

ใช้งานได้เพราะหากคุณเพียงนำค่าออกจาก API และจะส่งคืนบางสิ่งที่เฉพาะเจาะจง (เช่นstring) คุณสามารถจัดการกับค่าที่ส่งคืนเป็นประเภททั่วไป (เช่นobject)

contravariance

ตัวอย่างที่ยอมรับได้: IComparer<in T>,Action<in T>

คุณสามารถแปลงจากIComparer<object>เป็นIComparer<string>หรือAction<object>เป็นAction<string>; ค่าเข้าไปในวัตถุเหล่านี้เท่านั้น

เวลานี้ใช้งานได้เพราะหาก API คาดหวังบางสิ่งทั่วไป (เช่นobject) คุณสามารถให้บางสิ่งที่เฉพาะเจาะจงมากขึ้น (เช่นstring)

ให้เป็นปกติมากกว่านี้

หากคุณมีอินเตอร์เฟซIFoo<T>มันสามารถ covariant ในT(เช่นประกาศว่าราวกับIFoo<out T>ว่าTใช้เฉพาะในตำแหน่งเอาต์พุต (เช่นชนิดส่งคืน) ภายในอินเตอร์เฟสมันสามารถ contravariant ในT(เช่นIFoo<in T>) ถ้าTใช้เฉพาะในตำแหน่งอินพุต ( เช่นประเภทพารามิเตอร์)

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


1
สำหรับคนอย่างฉันจะเป็นการดีกว่าถ้าคุณเพิ่มตัวอย่างที่แสดงว่าอะไรคือความแปรปรวนร่วมและอะไรที่ไม่แปรปรวนและอะไรที่ไม่ใช่ทั้งคู่
bjan

1
@Jon Skeet ตัวอย่างที่ดีฉันไม่เข้าใจ"พารามิเตอร์ประเภทAction<T>ยังคงใช้เฉพาะTในตำแหน่งเอาต์พุต"เท่านั้น Action<T>return type เป็นโมฆะมันจะใช้Tเป็น output ได้อย่างไร? หรือว่ามันหมายความว่าอะไรเพราะมันไม่ส่งคืนสิ่งใดที่คุณเห็นว่ามันไม่สามารถละเมิดกฎได้?
Alexander Derck

2
สำหรับตัวฉันในอนาคตที่กลับมาที่คำตอบที่ยอดเยี่ยมนี้อีกครั้งเพื่อเรียนรู้ความแตกต่างนี่คือสิ่งที่คุณต้องการ: "[ความแปรปรวนร่วม] ทำงานได้เพราะถ้าคุณเพียงนำค่าออกมาจาก API และมันจะคืนค่าบางอย่าง เฉพาะ (เช่นสตริง) คุณสามารถใช้ค่าที่ส่งคืนเป็นชนิดทั่วไป (เช่นวัตถุ) "
Matt Klein

ส่วนที่สับสนที่สุดของทั้งหมดนี้ก็คือสำหรับความแปรปรวนร่วมหรือความขัดแย้งหากคุณไม่สนใจทิศทาง (เข้าหรือออก) คุณจะได้รับการแปลงที่เฉพาะเจาะจงมากขึ้นสำหรับ Generic More ต่อไป! ผมหมายถึง "คุณสามารถรักษาที่คุ้มค่ากลับมาเป็นประเภททั่วไปมากขึ้น (เช่นวัตถุ)" สำหรับความแปรปรวนและ "API คาดว่าบางสิ่งบางอย่างทั่วไป (เช่นวัตถุ) คุณสามารถให้มันเป็นสิ่งที่เฉพาะเจาะจงมากขึ้น (เช่นสตริง)" สำหรับcontravariance สำหรับฉันเสียงพวกนี้เหมือนกัน!
XM มี. ค.

@AlexanderDerck: ไม่แน่ใจว่าทำไมฉันไม่ได้ตอบคุณก่อน ฉันเห็นด้วยมันไม่ชัดเจนและจะพยายามอธิบายให้ชัดเจน
Jon Skeet

16

ฉันหวังว่าโพสต์ของฉันจะช่วยให้ได้มุมมองที่ไม่เชื่อเรื่องภาษา

สำหรับการฝึกอบรมภายในของฉันฉันได้ทำงานกับหนังสือ "Smalltalk, Objects and Design (Chamond Liu)" ที่ยอดเยี่ยมและฉันขอแนะนำตัวอย่างต่อไปนี้

“ ความสอดคล้อง” หมายถึงอะไร แนวคิดคือการออกแบบลำดับชั้นชนิดที่ปลอดภัยด้วยชนิดที่ทดแทนได้สูง กุญแจสำคัญในการรับความสอดคล้องนี้คือความสอดคล้องตามประเภทย่อยหากคุณทำงานในภาษาที่พิมพ์แบบคงที่ (เราจะพูดถึงหลักการทดแทน Liskov (LSP) ในระดับสูงที่นี่)

ตัวอย่างการปฏิบัติ (รหัสเทียม / ไม่ถูกต้องใน C #):

  • ความแปรปรวนร่วม: สมมติว่านกที่วางไข่“ สม่ำเสมอ” ด้วยการพิมพ์แบบคงที่: หากประเภทนกวางไข่ชนิดย่อยของนกจะไม่วางไข่ย่อยหรือไม่? เช่นเป็ดประเภทวาง DuckEgg จากนั้นจะได้รับความสอดคล้อง ทำไมสิ่งนี้จึงสอดคล้องกัน เพราะในนิพจน์ดังกล่าว: Egg anEgg = aBird.Lay();การอ้างอิง aBird อาจถูกแทนที่โดยกฎหมายโดย Bird หรือโดย Duck เราพูดว่า return type เป็น covariant กับประเภทที่ Lay () กำหนดไว้ การแทนที่ของชนิดย่อยอาจส่งคืนชนิดที่พิเศษกว่า =>“ พวกเขาส่งมอบมากขึ้น”

  • การแปรปรวน: สมมติว่าเปียโนเป็นนักเปียโนที่สามารถเล่น“ สม่ำเสมอ” ด้วยการพิมพ์แบบคงที่: ถ้านักเปียโนเล่นเปียโนเธอจะสามารถเล่น GrandPiano ได้ไหม? จะไม่ Virtuoso เล่น GrandPiano หรือ (ถูกเตือน; มีการบิด!) นี้ไม่สอดคล้องกัน! เพราะในการแสดงออกดังกล่าว: aPiano.Play(aPianist);aPiano ไม่สามารถทดแทนเปียโนอย่างถูกกฎหมายหรือโดยแกรนด์เปียโน GrandPiano! GrandPiano สามารถเล่นได้โดย Virtuoso เท่านั้นนักเปียโนก็กว้างเกินไป! GrandPianos จะต้องเล่นได้ตามประเภททั่วไปมากขึ้นจากนั้นการเล่นจะสอดคล้องกัน เราบอกว่าประเภทพารามิเตอร์นั้นขัดแย้งกับประเภทที่ Play () กำหนดไว้ การแทนที่ของชนิดย่อยอาจยอมรับประเภททั่วไปมากขึ้น =>“ พวกมันต้องการน้อยกว่า”

กลับไปที่ C #:
เนื่องจาก C # เป็นภาษาที่พิมพ์แบบคงที่ "ตำแหน่ง" ของอินเทอร์เฟซของประเภทที่ควรจะร่วมกันหรือ contravariant (เช่นพารามิเตอร์และประเภทที่ส่งคืน) ต้องทำเครื่องหมายอย่างชัดเจนเพื่อรับประกันการใช้งาน / การพัฒนาที่สอดคล้องกัน เพื่อให้ LSP ทำงานได้ดี ในภาษาที่พิมพ์แบบไดนามิกความสอดคล้องของ LSP มักจะไม่เป็นปัญหาในคำอื่น ๆ ที่คุณสามารถกำจัด "มาร์กอัป" แบบร่วมและแบบ contravariant บนอินเทอร์เฟซ. Net และผู้ได้รับมอบหมายหากคุณใช้ประเภทไดนามิกในประเภทของคุณ - แต่นี่ไม่ใช่ทางออกที่ดีที่สุดใน C # (คุณไม่ควรใช้ไดนามิกในส่วนต่อประสานสาธารณะ)

กลับไปที่ทฤษฎี:
ความสอดคล้องที่อธิบายไว้ (ชนิดพารามิเตอร์ส่งคืน covariant / ประเภทพารามิเตอร์ contravariant) เป็นอุดมคติทางทฤษฎี (สนับสนุนโดยภาษา Emerald และ POOL-1) ภาษา oop บางภาษา (เช่น Eiffel) ตัดสินใจใช้ความสอดคล้องประเภทอื่นโดยเฉพาะ ยังพารามิเตอร์ประเภท covariant เพราะมันอธิบายความเป็นจริงได้ดีกว่าอุดมคติทางทฤษฎี ในภาษาที่พิมพ์แบบคงที่ความสอดคล้องที่ต้องการมักจะเกิดขึ้นได้ด้วยการใช้รูปแบบการออกแบบเช่น "การเยี่ยงอย่างสองครั้ง" และ "ผู้เยี่ยมชม" ภาษาอื่น ๆ ให้เรียกว่า "การแจกจ่ายหลายอย่าง" หรือหลายวิธี (โดยทั่วไปการเลือกฟังก์ชั่นการโอเวอร์โหลด ณเวลาทำงานเช่น CLOS) หรือรับผลตามที่ต้องการโดยใช้การพิมพ์แบบไดนามิก


คุณบอกว่าการแทนที่ของชนิดย่อยอาจส่งคืนชนิดที่พิเศษกว่า แต่นั่นไม่เป็นความจริงอย่างสมบูรณ์ หากBirdกำหนดไว้public abstract BirdEgg Lay();คุณจะDuck : Bird ต้องดำเนินการpublic override BirdEgg Lay(){}ดังนั้นการยืนยันของคุณที่BirdEgg anEgg = aBird.Lay();มีความแปรปรวนใด ๆ ก็ไม่เป็นความจริง การเป็นจุดเริ่มต้นของคำอธิบายตอนนี้จุดทั้งหมดหายไปแล้ว คุณจะแทนที่จะบอกว่าความแปรปรวนที่มีอยู่ภายในการดำเนินการที่ DuckEgg ถูกโยนไปโดยปริยาย BirdEgg ออกประเภทกลับ / หรือไม่ โปรดกำจัดความสับสนของฉัน
Suamere

1
หากต้องการตัดสั้น: คุณพูดถูก! ขอโทษสำหรับความสับสน. DuckEgg Lay()ไม่ใช่การแทนที่ที่ถูกต้องสำหรับEgg Lay() ใน C #และนั่นคือ crux C # ไม่สนับสนุนประเภทการคืนค่า covariant แต่ Java รวมถึง C ++ ทำได้ ฉันค่อนข้างอธิบายอุดมคติทางทฤษฎีโดยใช้ไวยากรณ์คล้าย C # ใน C # คุณต้องปล่อยให้ Bird และ Duck ใช้อินเทอร์เฟซทั่วไปซึ่ง Lay กำหนดไว้เพื่อให้ได้ผลตอบแทน covariant (เช่น out-spec) จากนั้นเข้าด้วยกัน!
นิโก้

1
ในฐานะที่เป็นความคิดเห็นของ Matt-Klein เกี่ยวกับคำตอบของ @ Jon-Skeet, "สำหรับตัวฉันในอนาคต": สิ่งที่ดีที่สุดสำหรับฉันที่นี่คือ "ต้องการน้อยลงและมอบมากขึ้น" เป็นตัวช่วยจำที่ยอดเยี่ยม! มันคล้ายกับงานที่ฉันหวังว่าจะต้องการคำแนะนำที่เฉพาะเจาะจงน้อยลง (คำขอทั่วไป) และยังมอบบางสิ่งที่เฉพาะเจาะจงมากขึ้น (ผลิตภัณฑ์งานจริง) วิธีการเรียงลำดับของชนิดย่อย (LSP) ไม่ว่าจะด้วยวิธีใด
karfus

@karfus: ขอบคุณ แต่ฉันจำได้ว่าฉันถอดความคิดที่ว่า "ต้องการน้อยลงและให้มากขึ้น" จากแหล่งอื่น อาจเป็นหนังสือของ Liu ที่ฉันอ้างถึงด้านบน ... หรือแม้แต่. NET Rock talk Btw ใน Java คนลดความจำเป็น "PECS" ซึ่งเกี่ยวข้องโดยตรงกับวิธีการทางวากยสัมพันธ์เพื่อประกาศความแปรปรวน PECS สำหรับ "Producer extends, Consumer super"
Nico

5

ผู้รับมอบสิทธิ์แปลงช่วยให้ฉันเข้าใจความแตกต่าง

delegate TOutput Converter<in TInput, out TOutput>(TInput input);

TOutputแสดงให้เห็นถึงความแปรปรวนที่วิธีการส่งกลับประเภทที่เฉพาะเจาะจงมากขึ้น

TInputแสดงให้เห็นถึงcontravarianceที่วิธีการที่จะผ่านประเภทที่เฉพาะเจาะจงน้อย

public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }

public static Poodle ConvertDogToPoodle(Dog dog)
{
    return new Poodle() { Name = dog.Name };
}

List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();

0

ความแปรปรวนของ Co และ Contra นั้นค่อนข้างสมเหตุสมผล ระบบประเภทภาษาบังคับให้เราสนับสนุนตรรกะในชีวิตจริง เป็นตัวอย่างที่เข้าใจง่าย

แปรปรวน

ตัวอย่างเช่นคุณต้องการซื้อดอกไม้และคุณมีร้านดอกไม้สองแห่งในเมืองของคุณ: ร้านกุหลาบและร้านดอกเดซี่

ถ้าคุณถามใครสักคน "ร้านขายดอกไม้อยู่ที่ไหน" และมีคนบอกคุณว่าร้านกุหลาบอยู่ที่ไหน ใช่เพราะดอกกุหลาบเป็นดอกไม้ถ้าคุณต้องการซื้อดอกไม้คุณสามารถซื้อดอกกุหลาบได้ เช่นเดียวกันถ้ามีคนตอบคุณด้วยที่อยู่ของร้านเดซี่

นี่คือตัวอย่างของความแปรปรวนร่วม : คุณได้รับอนุญาตให้ส่งA<C>ไปA<B>ที่ซึ่งCเป็นคลาสย่อยของBหากAสร้างค่าทั่วไป (ส่งคืนเป็นผลมาจากฟังก์ชั่น) ความแปรปรวนร่วมเป็นเรื่องเกี่ยวกับผู้ผลิตนั่นคือเหตุผลที่ C # ใช้คำสำคัญoutเพื่อความแปรปรวนร่วม

ประเภท:

class Flower {  }
class Rose: Flower { }
class Daisy: Flower { }

interface FlowerShop<out T> where T: Flower {
    T getFlower();
}

class RoseShop: FlowerShop<Rose> {
    public Rose getFlower() {
        return new Rose();
    }
}

class DaisyShop: FlowerShop<Daisy> {
    public Daisy getFlower() {
        return new Daisy();
    }
}

คำถามคือ "ร้านขายดอกไม้อยู่ที่ไหน" คำตอบคือ "ร้านขายของที่นั่น":

static FlowerShop<Flower> tellMeShopAddress() {
    return new RoseShop();
}

contravariance

ตัวอย่างเช่นคุณต้องการของขวัญดอกไม้ให้แฟนของคุณและ Girlfrend ของคุณชอบดอกไม้ใด ๆ คุณคิดว่าเธอเป็นคนที่รักดอกกุหลาบหรือเป็นคนที่รักดอกเดซี่หรือไม่? ใช่เพราะถ้าเธอรักดอกไม้ใด ๆ เธอจะรักทั้งดอกกุหลาบและดอกเดซี่

นี่คือตัวอย่างของที่contravariance : คุณได้รับอนุญาตให้โยนA<B>ไปA<C>ที่Cเป็น subclass ของBถ้าAกินค่าทั่วไป ความแปรปรวนเป็นเรื่องเกี่ยวกับผู้บริโภคนั่นคือเหตุผลที่ C # ใช้คำสำคัญinเพื่อความแตกต่าง

ประเภท:

interface PrettyGirl<in TFavoriteFlower> where TFavoriteFlower: Flower {
    void takeGift(TFavoriteFlower flower);
}

class AnyFlowerLover: PrettyGirl<Flower> {
    public void takeGift(Flower flower) {
        Console.WriteLine("I like all flowers!");
    }
}

คุณกำลังพิจารณาแฟนสาวของคุณที่รักดอกไม้ใด ๆ ในฐานะคนที่รักดอกกุหลาบและให้ดอกกุหลาบแก่เธอ:

PrettyGirl<Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

การเชื่อมโยง

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