介面的實現方式(顯示和隱示)及協變和逆變

JoeSnail發表於2017-01-11

介面的實現方式(顯示和隱示)及協變和逆變

如果一個類繼承了兩個不同的介面,且這兩個介面有一樣的成員,類例項任意呼叫I1,I2介面:
如:

public interface I1
{
    string GetSome();
}

public interface I2
{
    string GetSome();
}
public class MyClass : I1, I2
{
    public string GetSome()
    {
        return "Some";
    }
}

MyClass c1 = new MyClass();

    I1 i1 = (I1) c1;
    I2 i2 = (I2) c1;

    c1.GetSome();
    i1.GetSome();
    i2.GetSome();

但是通常不同介面即使成員名稱相同,返回值相同,實現的目的功能還是不一樣的。所以區分介面還是非常必須要的。那麼如何在類裡區分繼承了兩個有相同約束的介面呢?

介面顯示實現

顯示實現I1介面(介面名稱.成員)

public class MyClass : I1, I2
{
    public string GetSome()
    {
        return "Some";
    }
    //介面成員的訪問修飾符預設為public,且不能顯示實現和修改,同樣顯示實現介面的類的成員也不可以有訪問修飾符。
    string I1.GetSome()
    {
        return "I1.Some";
    }
}

這時i1.GetSome()的輸出就是"I1.Some",i2.GetSome()和c1.GetSome()不變為"Some"


只顯示繼承I1介面,不實現I2介面

public class MyClass : I1, I2
{   
    string I1.GetSome()
    {
        return "I1.Some";
    }
}

編譯報錯,因為沒有實現介面I2。所以即使I1,I2有相同的約束,顯示實現是區分I1和I2的。隱示實現的話不區分I1,I2,可以只寫一個方法。


只顯示實現I1介面

public class MyClass : I1
{   
    string I1.GetSome()
    {
        return "I1.Some";
    }
}

只顯示實現介面I1,必須用介面來呼叫,不可以使用類例項來訪問GetSome()方法

//錯誤
MyClass my1=new MyClass();
my.GetSome();
//正確
I1 i1=(I1) my1;
i1.GetSome();
//正確
I1 i1=new MyClass();
i1.GetSome();

所以當類實現多個有衝突的成員的介面時,顯示使用介面可以解決這些衝突的介面成員。


泛型介面的協變和逆變

兩個有繼承關係的類

public class Parent { }
public class Child : Parent { }

類相互間轉換

Parent parent=new Child();//正常轉換
Child child=new Parent();//禁止轉換
Child child=(Child)new Parent();//禁止強制轉換

Parent[] arrParent = new Child[] {};//正常轉換

子類可以安全的轉換為父類,反之則不行。同理陣列間的轉換也遵循這個原則。

所以一個子類到父類的可變性稱之為協變,反之為逆變。具體在泛型介面上體現為引數的in,out關鍵字的使用。
例如:IEnumerable泛型的實現是IEnumerable<out T>,List泛型實現是List<T>。前者可以實現協變,後者不可以。所以只有新增了out關鍵字的泛型介面才可以實現協變(子類轉換為父類)。

泛型介面引數沒有關鍵字

public interface TI<T>{}
public class ParentCollection : TI<Parent>{}
public class ChildCollection : TI<Child>{}

TI<Parent> Parents = new ChildCollection();//編譯錯誤
TI<Child> Childs = new ParentCollection();//編譯錯誤

如果泛型介面的引數沒有使用in,out關鍵字,那麼不管是協變還是逆變都是不能實現的。


泛型介面引數帶有out關鍵字

public interface TI<out T>
{
    T Get();
}
public class ParentCollection : TI<Parent>
{
    public Parent Get()
    {
        return new Parent();
    }
}
public class ChildCollection : TI<Child>
{
    public Child Get()
    {
        return new Child();
    }
}  

呼叫實現out引數介面的類

TI<Parent> Parents = new ChildCollection();//協變轉換
TI<Child> Childs = new ParentCollection();//編譯錯誤,無法轉換

總結:實現out關鍵字引數的介面可以實現協變(子類轉換為父類)。

泛型引數介面帶有in關鍵字

public interface TI<in T>
{
    void Method(T param);
}
public class ParentCollection : TI<Parent>
{
    public void Method(Parent p){}
}

public class ChildCollection : TI<Child>
{
    public void Method(Child c){}
}

呼叫實現in引數介面的類

TI<Parent> Parents = new ChildCollection();//編譯錯誤
TI<Child> Childs = new ParentCollection();//逆變轉換

總結:帶有in引數介面的類可以實現逆變轉換(父類轉換為子類)

對比發現實現in,out引數介面的方法是不一樣的。

//協變
public interface TI<out T>
{
    T Get();
    void Method(T param);//編譯錯誤,只能定義在逆變的介面
}
//逆變
public interface TI<in T>
{
    T Get();//編譯錯誤,只能定義在協變的介面
    void Method(T param);
}

總結:協變的泛型型別只能作為輸出型別,不能作為輸入型別(out關鍵字只能影響輸出型別),逆變的泛型型別只能作為輸入型別,不能作為輸出型別(in關鍵字只能影響輸入型別)

相關文章