ตัวอย่างความแปรปรวนร่วมและความแปรปรวนของโลกแห่งความจริง


162

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

จนถึงตอนนี้มีเพียงตัวอย่างเดียวที่ฉันเห็นเป็นตัวอย่างอาร์เรย์แบบเดิม

object[] objectArray = new string[] { "string 1", "string 2" };

มันคงจะดีถ้าได้เห็นตัวอย่างที่อนุญาตให้ฉันใช้มันในระหว่างการพัฒนาของฉันถ้าฉันเห็นว่ามันถูกใช้ในที่อื่น


1
ผมสำรวจความแปรปรวนในการตอบคำถาม (ของตัวเอง): ประเภทแปรปรวน: โดยยกตัวอย่างเช่น ฉันคิดว่าคุณจะพบว่ามันน่าสนใจและให้คำแนะนำหวังว่า
Cristian Diaconescu

คำตอบ:


109

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

ดูเพิ่มเติมแปรปรวนและ contravariance ใน Generics ใน MSDN

ชั้นเรียน :

public class Person 
{
     public string Name { get; set; }
} 

public class Teacher : Person { } 

public class MailingList
{
    public void Add(IEnumerable<out Person> people) { ... }
}

public class School
{
    public IEnumerable<Teacher> GetTeachers() { ... }
}

public class PersonNameComparer : IComparer<Person>
{
    public int Compare(Person a, Person b) 
    { 
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : Compare(a,b);
    }

    private int Compare(string a, string b)
    {
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : a.CompareTo(b);
    }
}

การใช้งาน :

var teachers = school.GetTeachers();
var mailingList = new MailingList();

// Add() is covariant, we can use a more derived type
mailingList.Add(teachers);

// the Set<T> constructor uses a contravariant interface, IComparer<in T>,
// we can use a more generic type than required.
// See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax
var teacherSet = new SortedSet<Teachers>(teachers, new PersonNameComparer());

14
@FilipBartuzi - ถ้าอย่างฉันเมื่อฉันเขียนคำตอบนี้คุณมีงานทำในมหาวิทยาลัยที่เป็นตัวอย่างของโลกแห่งความจริง
tvanfosson

5
วิธีนี้สามารถทำเครื่องหมายคำตอบเมื่อไม่ตอบคำถามและไม่ให้ตัวอย่างของการใช้ความแปรปรวนร่วม / ต้านใน c #?
barakcaf

@barakcaf เพิ่มตัวอย่างของ contravariance ไม่แน่ใจว่าทำไมคุณไม่เห็นตัวอย่างของความแปรปรวนร่วม - คุณอาจต้องเลื่อนรหัสลง - แต่ฉันได้เพิ่มความคิดเห็นบางอย่างรอบ ๆ
tvanfosson

@tvanfosson โค้ดใช้ co / contra ฉันบอกว่ามันไม่แสดงวิธีการประกาศมัน ตัวอย่างไม่แสดงการใช้งานของ / ออกในการประกาศทั่วไปในขณะที่คำตอบอื่น ๆ ไม่
barakcaf

ดังนั้นถ้าฉันทำให้ถูกต้องความแปรปรวนร่วมคือสิ่งที่อนุญาตให้ใช้หลักการทดแทนของ Liskov ใน C # ได้ไหม?
Miguel Veloso

136
// Contravariance
interface IGobbler<in T> {
    void gobble(T t);
}

// Since a QuadrupedGobbler can gobble any four-footed
// creature, it is OK to treat it as a donkey gobbler.
IGobbler<Donkey> dg = new QuadrupedGobbler();
dg.gobble(MyDonkey());

// Covariance
interface ISpewer<out T> {
    T spew();
}

// A MouseSpewer obviously spews rodents (all mice are
// rodents), so we can treat it as a rodent spewer.
ISpewer<Rodent> rs = new MouseSpewer();
Rodent r = rs.spew();

เพื่อความสมบูรณ์ ...

// Invariance
interface IHat<T> {
    void hide(T t);
    T pull();
}

// A RabbitHat…
IHat<Rabbit> rHat = RabbitHat();

// …cannot be treated covariantly as a mammal hat…
IHat<Mammal> mHat = rHat;      // Compiler error
// …because…
mHat.hide(new Dolphin());      // Hide a dolphin in a rabbit hat??

// It also cannot be treated contravariantly as a cottontail hat…
IHat<CottonTail> cHat = rHat;  // Compiler error
// …because…
rHat.hide(new MarshRabbit());
cHat.pull();                   // Pull a marsh rabbit out of a cottontail hat??

138
ฉันชอบตัวอย่างที่เป็นจริงนี้ ฉันเพิ่งเขียนโค้ดส่งเสียงซุบซิบลาเมื่อสัปดาห์ที่แล้วและฉันดีใจมากที่ตอนนี้เรามีความแปรปรวนร่วม :-)
Eric Lippert

4
ความคิดเห็นด้านบนนี้กับ @javadba บอก THE EricLippert ความแปรปรวนร่วมและความแตกต่างเป็นตัวอย่าง covariant จริงของฉันบอกยายของฉันวิธีการดูดไข่! : p
iAteABug_And_iLiked_it

1
คำถามที่ไม่ได้ขอให้สิ่งที่ contravariance และความแปรปรวนสามารถทำมันถามว่าทำไมคุณจะต้องใช้มัน ตัวอย่างของคุณอยู่ไกลจากการใช้งานจริงเพราะมันไม่ต้องการเช่นกัน ฉันสามารถสร้าง QuadrupedGobbler และจัดการมันเอง (มอบหมายให้ IGobbler <Quadruped>) และมันยังสามารถฮุบ Donkeys ได้ (ฉันสามารถส่ง Donkey ไปยัง Gobble method ที่ต้องใช้ Quadruped) ไม่จำเป็นต้องมีความแตกต่างกัน มันเยี่ยมมากที่เราสามารถปฏิบัติต่อ QuadrupedGobbler ในฐานะ DonkeyGobbler ได้ แต่ทำไมเราต้องทำในกรณีนี้ถ้า QuadrupedGobbler สามารถฮุบ Donkeys ได้แล้ว?
wired_in

1
@wired_in เพราะเมื่อคุณเพียงแค่สนใจเกี่ยวกับลาการมีความเป็นอยู่ที่กว้างขึ้นสามารถเข้าไปได้ ตัวอย่างเช่นถ้าคุณมีฟาร์มที่ลาวัสดุสิ้นเปลืองที่จะ gobbled void feed(IGobbler<Donkey> dg)คุณสามารถแสดงนี้เป็น หากคุณใช้ IGobbler <Quadruped> เป็นพารามิเตอร์แทนคุณจะไม่สามารถผ่านมังกรที่กินลาได้
Marcelo Cantos

1
Waaay ไปงานปาร์ตี้ช้า แต่นี่เป็นเรื่องเกี่ยวกับตัวอย่างงานเขียนที่ดีที่สุดที่ฉันเคยเห็นรอบ ๆ ทำให้รู้สึกที่สมบูรณ์ในขณะที่ไร้สาระ ฉันจะต้องตอบคำถามของเกม ...
Jesse Williams

122

นี่คือสิ่งที่ฉันรวบรวมเพื่อช่วยให้ฉันเข้าใจความแตกต่าง

public interface ICovariant<out T> { }
public interface IContravariant<in T> { }

public class Covariant<T> : ICovariant<T> { }
public class Contravariant<T> : IContravariant<T> { }

public class Fruit { }
public class Apple : Fruit { }

public class TheInsAndOuts
{
    public void Covariance()
    {
        ICovariant<Fruit> fruit = new Covariant<Fruit>();
        ICovariant<Apple> apple = new Covariant<Apple>();

        Covariant(fruit);
        Covariant(apple); //apple is being upcasted to fruit, without the out keyword this will not compile
    }

    public void Contravariance()
    {
        IContravariant<Fruit> fruit = new Contravariant<Fruit>();
        IContravariant<Apple> apple = new Contravariant<Apple>();

        Contravariant(fruit); //fruit is being downcasted to apple, without the in keyword this will not compile
        Contravariant(apple);
    }

    public void Covariant(ICovariant<Fruit> fruit) { }

    public void Contravariant(IContravariant<Apple> apple) { }
}

TLDR

ICovariant<Fruit> apple = new Covariant<Apple>(); //because it's covariant
IContravariant<Apple> fruit = new Contravariant<Fruit>(); //because it's contravariant

10
นี่คือสิ่งที่ดีที่สุดที่ฉันเคยเห็นมาที่ชัดเจนและรัดกุมตัวอย่างที่ยอดเยี่ยม!
Rob L

6
วิธีการสามารถผลไม้จะ downcasted แอปเปิ้ล (ในContravarianceตัวอย่าง) เมื่อFruitเป็นแม่ของApple?
โทเบียส Marschall

@TobiasMarschall ซึ่งหมายความว่าคุณต้องศึกษาเพิ่มเติมเกี่ยวกับ "polymorphism"
snr

56

คำหลักเข้าและออกควบคุมกฎการส่งต่อคอมไพเลอร์สำหรับส่วนต่อประสานและผู้รับมอบสิทธิ์ด้วยพารามิเตอร์ทั่วไป

interface IInvariant<T> {
    // This interface can not be implicitly cast AT ALL
    // Used for non-readonly collections
    IList<T> GetList { get; }
    // Used when T is used as both argument *and* return type
    T Method(T argument);
}//interface

interface ICovariant<out T> {
    // This interface can be implicitly cast to LESS DERIVED (upcasting)
    // Used for readonly collections
    IEnumerable<T> GetList { get; }
    // Used when T is used as return type
    T Method();
}//interface

interface IContravariant<in T> {
    // This interface can be implicitly cast to MORE DERIVED (downcasting)
    // Usually means T is used as argument
    void Method(T argument);
}//interface

class Casting {

    IInvariant<Animal> invariantAnimal;
    ICovariant<Animal> covariantAnimal;
    IContravariant<Animal> contravariantAnimal;

    IInvariant<Fish> invariantFish;
    ICovariant<Fish> covariantFish;
    IContravariant<Fish> contravariantFish;

    public void Go() {

        // NOT ALLOWED invariants do *not* allow implicit casting:
        invariantAnimal = invariantFish; 
        invariantFish = invariantAnimal; // NOT ALLOWED

        // ALLOWED covariants *allow* implicit upcasting:
        covariantAnimal = covariantFish; 
        // NOT ALLOWED covariants do *not* allow implicit downcasting:
        covariantFish = covariantAnimal; 

        // NOT ALLOWED contravariants do *not* allow implicit upcasting:
        contravariantAnimal = contravariantFish; 
        // ALLOWED contravariants *allow* implicit downcasting
        contravariantFish = contravariantAnimal; 

    }//method

}//class

// .NET Framework Examples:
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { }
public interface IEnumerable<out T> : IEnumerable { }


class Delegates {

    // When T is used as both "in" (argument) and "out" (return value)
    delegate T Invariant<T>(T argument);

    // When T is used as "out" (return value) only
    delegate T Covariant<out T>();

    // When T is used as "in" (argument) only
    delegate void Contravariant<in T>(T argument);

    // Confusing
    delegate T CovariantBoth<out T>(T argument);

    // Confusing
    delegate T ContravariantBoth<in T>(T argument);

    // From .NET Framework:
    public delegate void Action<in T>(T obj);
    public delegate TResult Func<in T, out TResult>(T arg);

}//class

สมมติว่าปลาเป็นสัตว์ประเภทย่อย คำตอบที่ดีโดยวิธีการ
Rajan Prasad

48

นี่คือตัวอย่างง่ายๆโดยใช้ลำดับชั้นการสืบทอด

รับลำดับชั้นเรียนที่เรียบง่าย:

ป้อนคำอธิบายรูปภาพที่นี่

และในรหัส:

public abstract class LifeForm  { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }

ค่าคงที่ (เช่นพารามิเตอร์ประเภททั่วไป* ไม่ *ตกแต่งด้วยinหรือoutคำหลัก)

ดูเหมือนว่าวิธีการเช่นนี้

public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
    foreach (var lifeForm in lifeForms)
    {
        Console.WriteLine(lifeForm.GetType().ToString());
    }
}

... ควรยอมรับคอลเล็กชั่นที่หลากหลาย: (ซึ่งทำ)

var myAnimals = new List<LifeForm>
{
    new Giraffe(),
    new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra

อย่างไรก็ตามการส่งชุดสะสมที่ได้รับมามากกว่านั้นล้มเหลว

var myGiraffes = new List<Giraffe>
{
    new Giraffe(), // "Jerry"
    new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // Compile Error!

cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'

ทำไม? เพราะพารามิเตอร์ทั่วไปIList<LifeForm>ไม่ covariant - IList<T>คงที่เพื่อให้IList<LifeForm>ยอมรับเฉพาะคอลเลกชัน (ซึ่งใช้ IList) ซึ่งเป็นชนิดที่แปรต้องTLifeForm

หากการใช้วิธีการที่PrintLifeFormsเป็นอันตราย (แต่มีลายเซ็นวิธีการเดียวกัน) เหตุผลที่ทำให้คอมไพเลอร์ป้องกันการส่งผ่านList<Giraffe>จะเห็นได้ชัด:

 public static void PrintLifeForms(IList<LifeForm> lifeForms)
 {
     lifeForms.Add(new Zebra());
 }

เนื่องจากIListอนุญาตให้เพิ่มหรือลบองค์ประกอบคลาสย่อยใด ๆ ของLifeFormจึงสามารถเพิ่มไปยังพารามิเตอร์lifeFormsและจะละเมิดประเภทของคอลเลกชันประเภทใด ๆ ที่ได้รับผ่านไปยังวิธีการ (นี่คือวิธีการที่เป็นอันตรายจะพยายามที่จะเพิ่มZebraการvar myGiraffes) โชคดีที่คอมไพเลอร์ปกป้องเราจากอันตรายนี้

ความแปรปรวนร่วม (ทั่วไปกับประเภทพารามิเตอร์ตกแต่งด้วยout)

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

วิธีแก้ไขตัวอย่างข้างต้นคือเพื่อให้แน่ใจว่ามีการใช้ประเภทคอลเลกชันทั่วไป covariant เช่นIEnumerable(กำหนดเป็นIEnumerable<out T>) IEnumerableไม่มีวิธีที่จะเปลี่ยนเป็นการรวบรวมและเป็นผลมาจากoutความแปรปรวนร่วมการรวบรวมใด ๆ ที่มีชนิดย่อยLifeFormอาจถูกส่งผ่านไปยังวิธีการนี้:

public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms)
{
    foreach (var lifeForm in lifeForms)
    {
        Console.WriteLine(lifeForm.GetType().ToString());
    }
}

PrintLifeFormsขณะนี้สามารถเรียกว่ามีZebras, Giraffesและใด ๆIEnumerable<>ของประเภทรองใด ๆLifeForm

Contravariance (ทั่วไปพร้อมชนิดพารามิเตอร์ที่ตกแต่งด้วยin)

ความแปรปรวนมักใช้เมื่อฟังก์ชันถูกส่งผ่านเป็นพารามิเตอร์

นี่คือตัวอย่างของฟังก์ชั่นซึ่งใช้Action<Zebra>เป็นพารามิเตอร์และเรียกใช้งานบนอินสแตนซ์ที่รู้จักของ Zebra:

public void PerformZebraAction(Action<Zebra> zebraAction)
{
    var zebra = new Zebra();
    zebraAction(zebra);
}

ตามที่คาดไว้มันใช้งานได้ดี:

var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra"));
PerformZebraAction(myAction); // I'm a zebra

โดยสังเขปสิ่งนี้จะล้มเหลว:

var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe"));
PerformZebraAction(myAction); 

cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'

อย่างไรก็ตามสิ่งนี้สำเร็จ

var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal"));
PerformZebraAction(myAction); // I'm an animal

และสิ่งนี้ก็ประสบความสำเร็จด้วย:

var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba"));
PerformZebraAction(myAction); // I'm an amoeba

ทำไม? เนื่องจากActionมีการกำหนดเป็นAction<in T>กล่าวคือcontravariantหมายถึงว่าสำหรับAction<Zebra> myActionที่myActionสามารถที่ "มากที่สุด" a Action<Zebra>แต่ superclasses ที่ได้รับน้อยกว่าของZebraยังเป็นที่ยอมรับ

แม้ว่านี่อาจจะไม่ใช่งานง่ายในตอนแรก (เช่นว่าสามารถAction<object>ถูกส่งผ่านเป็นพารามิเตอร์ที่กำหนดAction<Zebra>?) ถ้าคุณแกะขั้นตอนที่คุณจะทราบว่าที่เรียกว่าฟังก์ชั่น ( PerformZebraAction) ตัวเองเป็นผู้รับผิดชอบสำหรับการส่งผ่านข้อมูล (ในกรณีนี้Zebraเช่น ) ไปยังฟังก์ชัน - ข้อมูลไม่ได้มาจากรหัสการโทร

เนื่องจากวิธีการกลับด้านของการใช้ฟังก์ชั่นการสั่งซื้อที่สูงขึ้นในลักษณะนี้ตามเวลาที่Actionถูกเรียกมันเป็นZebraอินสแตนซ์ที่ได้รับมากขึ้นซึ่งถูกเรียกใช้กับzebraActionฟังก์ชั่น (ผ่านเป็นพารามิเตอร์) แม้ว่าฟังก์ชั่นเอง


7
นี่คือคำอธิบายที่ดีสำหรับตัวเลือกที่แปรปรวนที่แตกต่างกันเพราะมันพูดผ่านตัวอย่างและยังชี้แจงเหตุผลที่จะ จำกัด คอมไพเลอร์หรือใบอนุญาตโดยไม่ต้องคำหลักเข้า / ออก
Vikhram

ไหนเป็นinคำหลักที่ใช้สำหรับcontravariance ?
javadba

@ javadba ด้านบนAction<in T>และFunc<in T, out TResult>มีความขัดแย้งในประเภทอินพุต (ตัวอย่างของฉันใช้ค่าคงที่ (รายการ) covariant (IEnumerable) และประเภท contravariant (Action, Func) ที่มีอยู่เดิม)
StuartLC

ตกลงฉันไม่ทำเช่นC#นั้นจะไม่ทราบว่า
javadba

มันค่อนข้างคล้ายกันใน Scala, ไวยากรณ์ที่แตกต่าง - [+ T] จะเป็น covariant ใน T, [-T] จะแปรปรวนใน T, Scala ยังสามารถบังคับใช้ข้อ จำกัด ระหว่าง 'และ' subclass promiscuous 'ซึ่งไม่มีอะไร ไม่มี
StuartLC

32
class A {}
class B : A {}

public void SomeFunction()
{
    var someListOfB = new List<B>();
    someListOfB.Add(new B());
    someListOfB.Add(new B());
    someListOfB.Add(new B());
    SomeFunctionThatTakesA(someListOfB);
}

public void SomeFunctionThatTakesA(IEnumerable<A> input)
{
    // Before C# 4, you couldn't pass in List<B>:
    // cannot convert from
    // 'System.Collections.Generic.List<ConsoleApplication1.B>' to
    // 'System.Collections.Generic.IEnumerable<ConsoleApplication1.A>'
}

โดยทั่วไปเมื่อใดก็ตามที่คุณมีฟังก์ชั่นที่รับ Enumerable ประเภทหนึ่งคุณจะไม่สามารถส่งผ่าน Enumerable ของประเภทที่ได้มาโดยไม่ต้องทำการแคสต์อย่างชัดเจน

เพียงเพื่อเตือนคุณเกี่ยวกับกับดักแม้ว่า:

var ListOfB = new List<B>();
if(ListOfB is IEnumerable<A>)
{
    // In C# 4, this branch will
    // execute...
    Console.Write("It is A");
}
else if (ListOfB is IEnumerable<B>)
{
    // ...but in C# 3 and earlier,
    // this one will execute instead.
    Console.Write("It is B");
}

นั่นเป็นรหัสที่น่ากลัวอยู่ดี แต่มันมีอยู่จริงและพฤติกรรมที่เปลี่ยนไปใน C # 4 อาจทำให้เกิดข้อผิดพลาดที่ละเอียดและยากที่จะหาหากคุณใช้โครงสร้างเช่นนี้


ดังนั้นสิ่งนี้จึงมีผลต่อคอลเลกชันมากกว่าสิ่งใดเพราะใน c # 3 คุณสามารถส่งประเภทที่ได้รับมากขึ้นไปยังวิธีที่เป็นประเภทที่ได้รับน้อยกว่า
มีดโกน

3
ใช่การเปลี่ยนแปลงครั้งใหญ่คือ IEnumerable สามารถรองรับสิ่งนี้ได้ในขณะที่ไม่เคยมีมาก่อน
Michael Stum

4

จากMSDN

ตัวอย่างโค้ดต่อไปนี้แสดงความแปรปรวนร่วมและการสนับสนุนความแปรปรวนสำหรับกลุ่มวิธี

static object GetObject() { return null; }
static void SetObject(object obj) { }

static string GetString() { return ""; }
static void SetString(string str) { }

static void Test()
{
    // Covariance. A delegate specifies a return type as object, 
    // but you can assign a method that returns a string.
    Func<object> del = GetString;

    // Contravariance. A delegate specifies a parameter type as string, 
    // but you can assign a method that takes an object.
    Action<string> del2 = SetObject;
}

4

contravariance

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

ในรหัสซึ่งหมายความว่าหากคุณมีIShelter<Animal> animalsคุณสามารถเขียนIShelter<Rabbit> rabbits = animals ถ้าคุณสัญญาและใช้TในIShelter<T>พารามิเตอร์วิธีเช่นเดียวกับ:

public class Contravariance
{
    public class Animal { }
    public class Rabbit : Animal { }

    public interface IShelter<in T>
    {
        void Host(T thing);
    }

    public void NoCompileErrors()
    {
        IShelter<Animal> animals = null;
        IShelter<Rabbit> rabbits = null;

        rabbits = animals;
    }
}

และแทนที่รายการด้วยรายการทั่วไปที่มากขึ้นเช่นลดความแปรปรวนหรือแนะนำความแปรปรวนทางตรงกันข้าม

แปรปรวน

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

ในรหัสซึ่งหมายความว่าถ้าคุณมีISupply<Rabbit> rabbitsคุณสามารถเขียนISupply<Animal> animals = rabbits ถ้าคุณสัญญาและใช้TในการISupply<T>เป็นวิธีการส่งกลับค่าเช่น:

public class Covariance
{
    public class Animal { }
    public class Rabbit : Animal { }

    public interface ISupply<out T>
    {
        T Get();
    }

    public void NoCompileErrors()
    {
        ISupply<Animal> animals = null;
        ISupply<Rabbit> rabbits = null;

        animals = rabbits;
    }
}

และแทนที่รายการด้วยสิ่งที่ได้รับมากกว่าเช่นเพิ่มความแปรปรวนหรือแนะนำความแปรปรวนร่วม

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

คุณอาจต้องการที่จะให้สิ่งนี้อ่านเพื่อห่อหัวของคุณรอบนี้


คุณสามารถกินเสือได้ ซึ่งมันคุ้มค่ากับการ
โหวต

ความคิดเห็นของคุณเกี่ยวกับcontravarianceน่าสนใจ ฉันกำลังอ่านมันเพื่อระบุความต้องการในการปฏิบัติงาน : ประเภททั่วไปที่มากขึ้นต้องรองรับกรณีการใช้งานทุกประเภทที่ได้รับจากมัน ดังนั้นในกรณีนี้ที่พักพิงสัตว์จะต้องสามารถรองรับการปกป้องสัตว์ทุกชนิดได้ ในกรณีนั้นการเพิ่มคลาสย่อยใหม่อาจทำให้ซูเปอร์คลาสแตก! นั่นคือ - ถ้าเราเพิ่มย่อยซอรัสเร็กซ์แล้วมันจะทำลายเราที่มีอยู่พักพิงสัตว์
javadba

(ต่อ) ซึ่งแตกต่างอย่างมากจากความแปรปรวนร่วมที่อธิบายไว้อย่างชัดเจนในเชิงโครงสร้าง : ประเภทย่อยที่เฉพาะเจาะจงทั้งหมดสนับสนุนการดำเนินการที่กำหนดในรูปแบบพิเศษ - แต่ไม่จำเป็นต้องในลักษณะเดียวกัน
javadba

3

ผู้รับมอบสิทธิ์แปลงช่วยให้ฉันเห็นภาพแนวคิดทั้งสองทำงานร่วมกัน:

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