介面的實現方式(顯示和隱示)及協變和逆變
如果一個類繼承了兩個不同的介面,且這兩個介面有一樣的成員,類例項任意呼叫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關鍵字只能影響輸入型別)