2010-02-25

Covariance og Contravariance (1 af 2)

Introduktion

I de næste 2 poster vil jeg forsøge at forklare hvordan covariance og contravariance kan anvendes i C#4 - selve begreberne covariance og contravariance er for komplekse til at forklare her, jeg vil istedet henvise til denne beskrivelse på wikipedia: Covariance_and_contravariance_(computer_science).

I denne første post vil jeg vise nogle eksempler på hvordan covariance fungerer i C#4.

Covariance

Her er et eksempel på simpel covariance. Jeg anvender her IEnumerable som understøtter covariance i C#4

interface ICoffee : IBeverage { }
interface IBeverage { }

[TestClass]
public class Covariance_Test
{
    [TestMethod]
    public void Simple_Covariance_Test()
    {
        IEnumerable<ICoffee> coffees = new List<ICoffee>();
        IEnumerable<IBeverage> beverages = coffees;
        Assert.AreEqual(coffees, beverages);
    }
}

Hvis man prøver at udføre det samme i C#3.0, vil man se at der kommer en kompilefejl, når man forsøger at tildele coffees til variablen beverages

Dette er meget simpelt eksempel, men er sandsynligvis nok det scenearie, hvor man oftest vil anvende denne nye funktionalitet. Man har også muligheden for at lade sine egne objekter understøtte covariance. Her er et eksempel hvor man ønsker at have nogle lister af objekter, der har det samme interface og vil kunne gennemgå data i disse lister uden at skulle lave en explicit cast.

Først er der interfacet og de klasser der implementerer dette interface
interface IDog
{
    string Name { get; set; }
}

class Beagle : IDog
{
    public string Name { get; set; }
}

class Basset : IDog
{
    public string Name { get; set; }
}

Herefter interfacet der beskriver hvordan listerne skal implementeres. Læg her mærke til keywordet "out" der fortæller at jeg ønsker at bruge paramteren T som en out parameter, dvs ikke kunne skrive direkte til den.
interface IKennel<out T> where T : IDog
{
    IEnumerable<T> dogs { get; }
}

Jeg har herefter lavet 2 klasser der implementere dette interface
class BeagleKennels : IKennel<Beagle>
{
    List<Beagle> beagles = new List<Beagle>();
    
    public IEnumerable<Beagle> dogs { get { return beagles; } }

    public BeagleKennels AddBeagle(Beagle beagle)
    {
        this.beagles.Add(beagle);
        return this;
    }
}

class BassetKennel : IKennel<Basset>
{
    List<Basset> bassets = new List<Basset>();

    public IEnumerable<Basset> dogs { get { return bassets; } }

    public BassetKennel AddBasset(Basset basset)
    {
        this.bassets.Add(basset);
        return this;
    }
}

Når jeg så anvender disse 2 Kennel typer i praksis, kan man se hvordan man nemt kan iterere over indholdet af disse 2 kenneler, uden at skulle lave en explicit cast til IDog
[TestMethod]
public void Custom_Covariance_Test()
{
    var TheBeagleKennel = new BeagleKennels()
        .AddBeagle(new Beagle() { Name = "C1_1" })
        .AddBeagle(new Beagle() { Name = "C1_2" });

    var TheBassetKennel = new BassetKennel()
        .AddBasset(new Basset() {Name="C2_1"})
        .AddBasset(new Basset() {Name="C2_2"});
    
    var classDs=new IKennel<IDog>[] {TheBeagleKennel,TheBassetKennel};
    var dogs= from kennel in kennels from dog in kennel.dogs select dog;
    foreach (var dog in dogs)
    {
       Assert.IsInstanceOfType(dog,typeof(IDog));
    }
}

Næste post

I næste post vil jeg vise nogle lignende eksempler på hvordan man kan anvende contravariance i C#4

Ingen kommentarer:

Tilføj en kommentar