ด้วย<out T>
คุณสามารถถือว่าการอ้างอิงอินเทอร์เฟซเป็นหนึ่งในลำดับชั้น
ด้วย<in T>
คุณสามารถใช้การอ้างอิงอินเทอร์เฟซเป็นแบบหนึ่งใน hiearchy
ให้ฉันพยายามอธิบายเป็นภาษาอังกฤษเพิ่มเติม
สมมติว่าคุณกำลังเรียกดูรายชื่อสัตว์จากสวนสัตว์ของคุณและคุณตั้งใจจะดำเนินการให้ สัตว์ทุกตัว (ในสวนสัตว์ของคุณ) มีชื่อและรหัสเฉพาะ สัตว์บางชนิดเป็นสัตว์เลี้ยงลูกด้วยนมบางชนิดเป็นสัตว์เลื้อยคลานบางชนิดเป็นสัตว์ครึ่งบกครึ่งน้ำบางชนิดเป็นปลา ฯลฯ แต่เป็นสัตว์ทั้งหมด
ดังนั้นด้วยรายชื่อสัตว์ของคุณ (ซึ่งมีสัตว์ประเภทต่างๆ) คุณสามารถพูดได้ว่าสัตว์ทุกตัวมีชื่อดังนั้นจึงปลอดภัยที่จะได้รับชื่อสัตว์ทั้งหมด
อย่างไรก็ตามถ้าคุณมีรายชื่อปลาเท่านั้น แต่ต้องปฏิบัติต่อพวกมันเหมือนสัตว์ล่ะจะได้ผลหรือไม่? โดยสัญชาตญาณควรใช้งานได้ แต่ใน C # 3.0 และก่อนหน้านี้ส่วนของโค้ดจะไม่รวบรวม:
IEnumerable<Animal> animals = GetFishes();
เหตุผลก็คือคอมไพเลอร์ไม่ "รู้" ว่าคุณตั้งใจหรือสามารถทำอะไรกับคอลเลกชันสัตว์หลังจากที่คุณได้รับมันแล้ว สำหรับทุกสิ่งที่ทราบอาจมีวิธีIEnumerable<T>
การนำวัตถุกลับเข้าไปในรายการและอาจทำให้คุณสามารถใส่สัตว์ที่ไม่ใช่ปลาลงในคอลเลกชันที่ควรจะมีปลาเท่านั้น
กล่าวอีกนัยหนึ่งคอมไพเลอร์ไม่สามารถรับประกันได้ว่าสิ่งนี้ไม่ได้รับอนุญาต:
animals.Add(new Mammal("Zebra"));
ดังนั้นคอมไพเลอร์จึงปฏิเสธที่จะรวบรวมโค้ดของคุณทันที นี่คือความแปรปรวนร่วม
มาดูความแตกต่างกัน
เนื่องจากสวนสัตว์ของเราสามารถรองรับสัตว์ได้ทุกชนิดจึงสามารถจับปลาได้อย่างแน่นอนดังนั้นเรามาลองเพิ่มปลาในสวนสัตว์ของเรากันดีกว่า
ใน C # 3.0 และก่อนหน้านี้จะไม่รวบรวม:
List<Fish> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
ที่นี่คอมไพเลอร์สามารถอนุญาตโค้ดชิ้นนี้ได้แม้ว่าเมธอดจะคืนค่าList<Animal>
เพียงเพราะปลาทั้งหมดเป็นสัตว์ดังนั้นหากเราเปลี่ยนประเภทเป็นสิ่งนี้:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
จากนั้นจะใช้งานได้ แต่คอมไพเลอร์ไม่สามารถระบุได้ว่าคุณไม่ได้พยายามทำสิ่งนี้:
List<Fish> fishes = GetAccessToFishes();
Fish firstFist = fishes[0];
เนื่องจากรายการนี้เป็นรายชื่อสัตว์จึงไม่อนุญาต
ดังนั้นความแตกต่างและความแปรปรวนร่วมคือวิธีที่คุณปฏิบัติต่อการอ้างอิงอ็อบเจ็กต์และสิ่งที่คุณได้รับอนุญาตให้ทำกับสิ่งเหล่านี้
in
และout
คำหลักใน C # 4.0 เครื่องหมายโดยเฉพาะอินเตอร์เฟซเป็นหนึ่งหรืออื่น ๆ ด้วยin
คุณได้รับอนุญาตให้วางประเภททั่วไป (โดยปกติคือ T) ในตำแหน่งอินพุตซึ่งหมายถึงอาร์กิวเมนต์ของวิธีการและคุณสมบัติเขียนอย่างเดียว
ด้วยout
คุณได้รับอนุญาตให้วางประเภททั่วไปในเอาต์พุต -ตำแหน่งซึ่งเป็นวิธีการคืนค่าคุณสมบัติอ่านอย่างเดียวและพารามิเตอร์วิธีการออก
สิ่งนี้จะช่วยให้คุณทำสิ่งที่ตั้งใจจะทำกับโค้ด:
IEnumerable<Animal> animals = GetFishes();
List<T>
มีทั้งในและนอกทิศทางบน T ดังนั้นจึงไม่ใช่ตัวแปรร่วมหรือตัวแปรตรงกันข้าม แต่เป็นอินเทอร์เฟซที่อนุญาตให้คุณเพิ่มวัตถุเช่นนี้:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
จะอนุญาตให้คุณทำสิ่งนี้:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals();
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
นี่คือวิดีโอบางส่วนที่แสดงแนวคิด:
นี่คือตัวอย่าง:
namespace SO2719954
{
class Base { }
class Descendant : Base { }
interface IBibbleOut<out T> { }
interface IBibbleIn<in T> { }
class Program
{
static void Main(string[] args)
{
IBibbleOut<Base> b = GetOutDescendant();
IBibbleIn<Descendant> d = GetInBase();
}
static IBibbleOut<Descendant> GetOutDescendant()
{
return null;
}
static IBibbleIn<Base> GetInBase()
{
return null;
}
}
}
หากไม่มีเครื่องหมายเหล่านี้สิ่งต่อไปนี้สามารถรวบรวม:
public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
หรือสิ่งนี้:
public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
as Descendants