ใน C# concept ที่เรียกว่า covariance และ contravariance ถูกใช้กับอะไรบ้าง
- ใช้กับ array ในเวอร์ชัน 1.0
- ใช้กับ delegate ในเวอร์ชั่น 2.0
- ใช้กับ generic ในเวอร์ชั่น 4.0
Substitution
ก่อนจะรู้จักกับ covariance ต้องทำความเข้าใจกับหลักการ substitution ใน C# เสียก่อน
1 | public class Animal { .. } |
จาก class hierarchy จะเห็นว่า Animal เป็น supertype ของ Cat
1 | Animal a = new Cat(); // ok |
จากตัวอย่าง Animal
เป็นเซ็ตที่ใหญ่กว่าตามหลัก substitution principle เราสามารถใช้ instance ของ Cat
แทน instance ของ Animal
ได้ แต่ไม่สามารถใช้ instance ของ Animal
แทน Cat
ให้นึกภาพว่า เราไม่สามารถนำกล่องที่ใหญ่มาใส่ในกล่องที่เล็กกว่านั่นเอง
Array
Array ที่มี element เป็น reference type ใน C# ถือว่าเป็น covariance
1 | Animal[] animals = new Cat[10]; |
จากตัวอย่าง Animal[]
compatible กับ Cat[]
สามารถใช้ instance ของ Cat
แทน Animal
เราเรียกการ assign ค่าที่ type เล็กกว่าอยู่ด้านขวาของเครื่องหมาย =
ว่า covariance
แต่ covariance ของ array เป็น covariance แบบไม่ safe เนื่องจากเราสามารถใส่ instance ของคลาสอื่นนอกเหนือจาก Cat
แต่อยู่ hierarch ถัดจาก Animal
ซึ่งจะทำให้เกิด ArrayTypeMismatchException
ขณะรัน
1 | // Attempted to access an element as a type incompatible with the array. |
จากตัวอย่างจะเกิด error ขณะรัน เนื่องจาก backing store ของ array ถูกกำหนดให้เก็บ Cat
ตั้งแต่ต้น จึงไม่สามารถเก็บ element ที่มี type อื่นได้ ถึงแม้จะเป็น subtype ของ Animal
เหมือนกันก็ตาม
Covariance ใน array ถูก implement ตั้งแต่ C# 1.0 เพื่อให้เหมือนกับ Java ในขณะนั้น ถึงปัจจุบันปัญหา TypeMissMatch
ก็ยังอยู่ ไม่สามารถตรวจสอบขณะ compile ได้
ใน C# รุ่นต่อมา มีการ implement covariance กับส่วนอื่น ซึ่งจะไม่มีปัญหาลักษณะนี้ เนื่องจากมีการออกแบบให้มีความปลอดภัยตั้งแต่ต้น
Method group
Covariance
1 | Cat MakeCat() { ... } |
Covariant จะทำให้ Func<Animal>
compatible กับ MakeCat
เนื่องจาก Animal
ใหญ่กว่า Cat
จำสามารถทำ implicit conversion
Contravaraince
ตัวอย่างนี้มีสอง method คือ ShowCat
ที่รับ Cat
เป็น parameter และ Animal
ที่รับ Animal
เป็น parameter
1 | void ShowCat(Cat c) {} |
จากตัวอย่างเราสามารถ assign method ไปยัง delegate Action
ได้ดังนี้
1 | Action<Cat> action2 = ShowAnimal; // legal |
จากหลัก substution principle Cat
นั้นเล็กว่า Animal
- บรรทัดแรก
Action<Cat>
compatible กับShowAnimal
เนื่องจาก เราสามารถใช้Cat
แทนAnimal
ได้ ในตัวอย่างนี้ถึงแม้จะใช้หลักsubstitution
เหมือนเดิม แต่ทิศทางการ assign ค่าด้วยเครื่องหมาย=
จะกลับข้างกัน เราเรียก operation ลักษณะนี้ว่าcontravariance
- บรรทัดที่สอง
Action<Animal>
ไม่สามารถใช้แทนShowCat
ได้ เนื่องจากจะผิดหลักsubstitution
เพราะว่าAnimal
ใหญ่กว่าCat
จึงไม่สามารถใช้Animal
แทนCat
นั่นเอง
Generic delegate
ใน C# 4.0 มีสิ่งที่เรียกว่า safe generic variant ที่ใช้ได้กับ interface และ delegate
1 | public delegate TResult Func<in T, out TResult>(T arg); |
จากตัวอย่าง เราสามารถประกาศ delegate ชื่อ Func
ที่มี type parameter ตัวแรกเป็น contravariance T
และมี type parameter ตัวที่สองเป็น covariance TResult
- ใช้ keyword
in
เพื่อระบุว่า type parameter เป็น contravariance - ใช้ keyword
out
เพื่อระบุว่า type parameter เป็น variance
เรียกใช้งาน
1 | Func<Animal, Cat> f1 = (x) => new Cat(); |
Func<Animl,Cat>
compatible กับ Func<Cat, Animal>
เนื่องจากจาก type parameter ตัวแรกเป็น contravariance Cat
ซึ่งเป็น type ที่เล็กกว่าจึงสามารถส่งเข้าไปในฟังก์ชั่นแทน Animal
ส่วน type parameter ตัวที่สองเป็น covariance เราจึงใช้ Animal
เพื่อรับค่าแทน Cat
ที่ return มาจากฟังกํชั่นได้เช่นเดียวกัน
Links