前言
簡單整理一下逆變和協變。
正文
什麼是逆變和協變呢?
首先逆變和協變都是術語。
協變表示能夠使用比原始指定的派生型別的派生程度更大的型別。
逆變表示能夠使用比原始指定的派生型別的派生程度更小的型別。
這裡student 繼承 person。
這裡這個報錯合情合理。
這裡可能有些剛入門的人認為,person 不是 student 的父類啊,為什麼不可以呢?
一個列表student 同樣也是一個列表的 person啊。
這可能是初學者的一個疑問。
但是實際情況是list
所以他們無法隱式轉換是正常的。
但是這樣寫就可以:
static void Main(string[] args)
{
IEnumerable<Student> students = new List<Student>();
IEnumerable<Person> peoples = students;
}
這樣寫沒有報錯,理論上IEnumerable
為什麼呢?因為支援協變。
協變表示能夠使用比原始指定的派生型別的派生程度更大的型別。
他們的結構如上。因為student是person的派生類,IEnumerable
協變怎麼宣告呢:
public interface IEnumerable<out T> : IEnumerable
{
//
// 摘要:
// Returns an enumerator that iterates through the collection.
//
// 返回結果:
// An enumerator that can be used to iterate through the collection.
new IEnumerator<T> GetEnumerator();
}
這裡協變有個特點,那就是協變引數T,只能用於返回型別。
原因是在執行時候還是new List
那麼協變引數T,不能用於引數呢? 是這樣的。
比如 IEnumerable
public void test(T a)
{
}
在IEnumerable
person 要轉換成student,顯然是不符合的。
那麼協變是這樣的,那麼逆變呢?
public interface ITest<in T>
{
public void Run(T obj);
}
public class Test<T> : ITest<T>
{
public void Run(T obj)
{
throw new NotImplementedException();
}
}
然後這樣使用:
static void Main(string[] args)
{
ITest<Person> students = new Test<Person>();
ITest<Student> peoples = students;
peoples.Run(new Student());
}
這裡的逆變只能作用於引數。
先說一下為什麼能夠作用於引數,就是在執行的時候本質還是new Test
然後為什麼不能作用於返回值呢?
假如ITest 可以這樣:
public interface ITest<in T>
{
public T Run()
{
}
}
在執行時候是Test
所以協變不能作用於引數,逆變不能作用於返回值。
那麼也就是說要摸只能協變,要摸只能逆變。
下面是委託中的逆變:
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());
原理就是Derived繼承自Base,原本需要傳入base,現在傳入Derived,當然也是可以的。
之所以這麼設計是一個哲學問題,那就是子類可以賦值給父類,父類能辦到的子類也能辦到,他們分別對應的是協變和逆變。
結
下一節委託。