首頁 > 軟體

C#泛型介面的協變和逆變

2022-04-11 16:00:38

1、什麼是協變、逆變?

假設:TSub是TParent的子類。
協變:如果一個泛型介面IFoo<T>,IFoo<TSub>可以轉換為IFoo<TParent>的話,我們稱這個過程為協變,IFoo支援對引數T的協變。
逆變:如果一個泛型介面IFoo<T>,IFoo<TParent>可以轉換為IFoo<TSub>的話,我們稱這個過程為逆變,IFoo支援對引數T的逆變。

2、為什麼要有協變、逆變?

通常只有具備繼承關係的物件才可以發生隱式型別轉換,如Base b=new sub()。
協變和逆變可以使得更多的型別之間能夠實現隱式型別轉換、型別安全性有了保障。

3、為什麼泛型介面要引入協變、逆變?

基於以上原因的同時、許多介面僅僅將型別引數用於引數或返回值。所以支援協變和逆變後泛型的使用上有了更大的靈活性

4、為什麼支援協變的引數只能用於方法的返回值?支援逆變的引數只能用於方法引數?

“TParent不能安全轉換成TSub”,是這兩個問題的共同原因。
我們定義一個介面IFoo。

    interface IFoo<T>
    {
        void Method1(T param);
        T Method2();
    }

我們看一下協變的過程:IFoo<TSub>轉換成IFoo<TParent>。

  • Method1:將TSub替換成TParent,Method1顯然存在 TParent到TSub的轉換。
  • Method2:返回值型別從TSub換成了TParent,是型別安全的。

所以支援協變的引數只能用在方法的返回值中。

再看一下逆變的過程:IFoo<TParent>轉換成IFoo<TSub>。

  • Method1:將TParent替換成TSub,Method1存在 TSub到TParent的轉換,是型別安全的。
  • Method2:返回值型別從TParent換成了TSub,是不安全的。

所以支援逆變的引數只能用在方法的引數中。

5、泛型介面支援協變、逆變和不支援協變、逆變的對比?

這其實是對3個問題的補充。

定義一個介面IFoo,既不支援協變,也不支援逆變。

    interface IFoo<T>
    {
        void Method1(T param);
        T Method2();
    }

實現介面IFoo

    public class FooClass<T> : IFoo<T>
    {
        public void Method1(T param)
        {
            Console.WriteLine(default(T));
        }
        public T Method2()
        {
            return default(T);
        }
    }

定義一個介面IBar支援對引數T的協變

    interface IBar<out T>
    {
        T Method();
    }

實現介面IBar

    public class BarClass<T> : IBar<T>
    {
        public T Method()
        {
            return default(T);
        }
    }

 定義一個介面IBaz支援對引數T的逆變

    interface IBaz<in T>
    {
        void Method(T param);
    }

實現介面IBaz

    public class BazClass<T> : IBaz<T>
    {
        public void Method(T param)
        {
            Console.WriteLine(param.ToString());
        }
    }

定義兩個有繼承關係的型別,IParent和SubClass。

    interface IParent
    {
        void DoSomething();
    }
    public class SubClass : IParent
    {
        public void DoSomething()
        {
            Console.WriteLine("SubMethod");
        }
    }

按照協變的邏輯,分別來使用IFoo和IBar。

            //IFoo 不支援對引數T的協變
            IFoo<SubClass> foo_sub = new FooClass<SubClass>();
            IFoo<IParent> foo_parent = foo_sub;//編譯錯誤

            //IBar 支援對引數T的協變
            IBar<SubClass> bar_sub = new BarClass<SubClass>();
            IBar<IParent> bar_parent = bar_sub;

foo_parent = foo_sub 會提示編譯時錯誤“無法將型別“IFoo<SubClass>”隱式轉換為“IFoo<IParent>”。存在一個顯式轉換(是否缺少強制轉換?)”

按照逆變的邏輯,分別來使用IFoo和IBaz。

            //IFoo 對引數T逆變不相容
            IFoo<IParent> foo_parent = null;
            IFoo<SubClass> foo_sub = foo_parent;//編譯錯誤

            //IBaz 對引數T逆變相容
            IBaz<IParent> baz_parent = null;
            IBaz<SubClass> baz_sub = baz_parent;

 foo_sub = foo_parent 會提示編譯時錯誤“無法將型別“IFoo<IParent>”隱式轉換為“IFoo<ISub>”。存在一個顯式轉換(是否缺少強制轉換?)”

6、.NET4.0對IEnumerable介面的修改?

2.0中的定義:

    public interface IEnumerable<T> : IEnumerable
    {
        IEnumerator<T> GetEnumerator();
    }

4.0中的定義:

    public interface IEnumerable<out T> : IEnumerable
    {
        IEnumerator<T> GetEnumerator();
    }

可以看到4.0中增加了對協變的支援。

可以在兩個版本試下, 下面的語句在2.0下會報錯。

    List<SubClass> subarr = new List<SubClass>();
    IEnumerable<IParent> parentarr = subarr;

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com