接口的实现方式(显示和隐示)及协变和逆变

接口的实现方式(显示和隐示)及协变和逆变

如果一个类继承了两个不同的接口,且这两个接口有一样的成员,类实例任意调用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关键字只能影响输入类型)

posted @ 2017-01-11 16:20  JoeSnail  阅读(616)  评论(0编辑  收藏  举报
web
statistics