2014年5月10日 星期六

委拖(delegate)的協變與逆變

建立一個介面和一個實現介面的類別
internal interface IVehicle
{
    string Name { getset; }
}
 
internal class Car : IVehicle
{
    public string Name { getset; }
 
    public int Doors { getset; }
}

使用熟悉的類型進行演示
delegate T Func<out T>
delegate void Action<in T>(T obj)

使用 Lambda表達式可以很輕易地進行演示,甚至可以將他們連接起來
Func<Car> carFactory = () => new Car { Name = "BMW", Doors = 4 };
Func<IVehicle> vehicleFactory = carFactory; // 使用協變性轉換 Func<T>
 
Action<IVehicle> vehicleShow = vehicle => Console.WriteLine(vehicle.Name);
Action<Car> CarShow = vehicleShow; // 使用逆變性轉換 Action<T>
 
// 完整檢查
CarShow(carFactory()); // BMW
vehicleShow(vehicleFactory()); // BMW

協變性允許我們將汽車工廠視為更一般的載具(交通工具)工廠。
創建一個通用的行為,打印任何載具的名稱,使用逆變轉換,讓行為可用於人和載具。
最後將汽車工廠的結果提供給汽車展行為(action),將載具工廠的結果給載具秀行為,結果都是BMW

查看 vehicleFactor() 執行的結果
Console.WriteLine(vehicleFactory().Name);
Console.WriteLine(((Car)vehicleFactory()).Doors);
第一行顯示 BMW
第二行必須轉換類行為 Car 才能查看 Doors 屬性

介面(interface)的協變與逆變

介面(interface)的逆變與協變無法在 C# 3.0 進行編譯

定義一個介面和繼承該介面的兩個類別
internal interface IAnimal
{
    string Name { getset; }
 
    int Age { getset; }
}
 
internal class Dog : IAnimal
{
    public string Name { getset; }
 
    public int Age { getset; }
 
    public string Size { getset; }
}
 
internal class Bird : IAnimal
{
    public string Name { getset; }
 
    public int Age { getset; }
 
    public bool IsFlying { getset; }
}

實例化兩個List<T>物件
List<Dog> dogs = new List<Dog>
{
    new Dog{Name="Dog 1",Age=3, Size="Small"},
    new Dog{Name="Dog 2",Age=5, Size="Big"}
};
List<Bird> birds = new List<Bird>
{
    new Bird{Name="Bird 1",Age=4,IsFlying=true},
    new Bird{Name="Bird 2",Age=2,IsFlying=false}
};

演示 IEnumerable<out T>協變介面
1.
List<IAnimal> animals = new List<IAnimal>();
animals.AddRange(dogs);
animals.AddRange(birds);
建立一個 List<IAnimal>, 並調用 AddRange 向其添加 Dog 和 Bird列表(List);
List<T>.AddRange 的參數為 IEnumerable<T> 類型,
因此這種情況下,將這兩個列表都看成是 IEnumerable<IAnimal>,而這以前是不允許的。

2.
List<IAnimal> concat = dogs.Concat<IAnimal>(birds).ToList();
使用LINQ方法,根據已知序列的數據創建列表;
不能直接調用 dogs.Concat(birds),這會使類型推斷機制變得混亂, 應該顯示地指定類型參數。
dogs 和 cats 都將根據協變性而隱式轉換為 IEnumerable<IAnimal>, 這種轉換不會真正改變它們的值,所改變的只是編譯器如何看待這些值。

結果:
foreach (var item in animals)
{
    Console.WriteLine(item.Name);
}
顯示 :
Dog 1
Dog 2
Bird 1
Bird 2

無法使用 item.Size 或是 item.IsFlying, 除非轉為 Dog 或是 Bird:
foreach (var item in animal)
{
    if (item is Bird)
    {
        var bird = (Bird)item;
        Console.WriteLine("the bird is flying? {0}", bird.IsFlying);
    }
}

演示 IComparer<in T> 逆變介面
internal class AgeComparer : IComparer<IAnimal>
{
    public int Compare(IAnimal x, IAnimal y)
    {
        return x.Age.CompareTo(y.Age);
    }
}
自定 AgeComparer 類別,實現 IComparer<T>
....
IComparer<IAnimal> ageComparer = new AgeComparer();
dogs.Sort(ageComparer);
使用逆變性,進行排序;有了 IComparer<IAnimal>,就可以用她進行排序,dogs.Sort 的參數應該為 IComparer<Dog> 類型,但逆變性會進行隱式轉換。