C#调用接口方法非虚拟实现

时间:2018-09-06 11:51:14

标签: c# interface non-virtual-interface

我是C#的新手,我不明白为什么编译器不会在此代码上抱怨。这是类的层次结构:

interface IAble
{
    void f();
}

class AAble : IAble
{
    public void f()
    {
        Debug.Log("---->> A - Able");
    }
}

class BAble : AAble
{
    public void f()
    {
        Debug.Log("---->> B - Able");
    }
}

执行代码:

        IAble i = new BAble();
        i.f();

在执行时---->> A - Able被打印。为什么?编译器如何知道应调用什么函数?

决定要调用的函数是运行时还是编译时?如果我de污新的类class CAble : IAble怎么办?

4 个答案:

答案 0 :(得分:2)

由于AAble正在实现IAble接口,因此其AAble.f被标记为IAble.f类型的AAble方法的实现。

BAble.f只是隐藏AAble.f方法,而不是覆盖它。

IAble o = new BAble(); o.f(); // calls AAble.f
AAble o = new BAble(); o.f(); // calls AAble.f
BAble o = new BAble(); o.f(); // calls BAble.f
IAble o = new CAble(); o.f(); // calls CAble.f

决定是在编译时做出的:

// AAble.f in IL:
.method public final hidebysig newslot virtual 
    instance void f () cil managed 

// BAble.f in IL:
.method public hidebysig 
    instance void f () cil managed

接口实现在IL中被标记为virtual,即使在C#中未将其标记为虚拟。该方法在IL中也被标记为final,如果在C#中该方法将被标记为virtual,那么它就不会被标记为final

答案 1 :(得分:1)

通常情况下,会因为隐藏方法而出现编译器警告。但在C#中,对非虚拟功能执行合法操作。但是,当然,如果它是一个虚函数,则显然可以运行该方法的B版本。

由于您将其声明为IAble并且不是虚拟的,因此编译器将其读取为IAble。如果将其声明为虚拟的,则编译器将扫描继承的层次结构,并发现它的实际类是BAble,并且它将运行BAble代码。

答案 2 :(得分:1)

当您在派生类中定义与基类具有相同签名的方法时,您将隐藏

这意味着当您声明具有基类型的变量并以dervied类型对其进行初始化时,将使用基类中的方法。这就是代码中的笨拙。

更笼统:当您隐藏方法时,然后是将要使用的方法的版本,即声明时使用的类的信息。

因此,如果您还有另一个类CAble并按如下方式使用:

BAble c = new CAble();
b.f();

那么结果将是---->> B - Able

在您的情况下,您将可变参数声明为IAble。它没有实现,因此它着眼于在类AAble中定义的实现。其他类仅隐藏该方法。

为了隐藏方法,您可以指定两个具有相同签名的方法。但是,您应该始终使用new键来显式隐藏该方法(这将表明该隐藏是有意的)。

您期望的是覆盖的方法,这些方法是在定义方法时通过使用override keywaord来实现的。

为了重写方法,应在基类中将其标记为virtual(如果有实现)或abstract(如果没有实现)。

答案 3 :(得分:1)

接口必须在直接从其继承的类中实现,而不是在派生类之一中实现。例如,此代码将无法编译:

class AAble : IAble
{
    public void f() { ... }
}

class BAble : AAble
{
    // An attempt to explicitly implement interface in BAble through AAble class
    void IAble.f()
    {
        Console.WriteLine("---->> B - Able");
    }
}

当我们将BAble转换为接口IAble时,将使用AAble的实现,因为它是编译预期中唯一实现该接口的类。

我们可以直接从接口继承,这将告诉编译器应该使用哪种接口实现:

class BAble : AAble, IAble
{
    // Now it compiles
    void IAble.f()
    {
        Console.WriteLine("---->> B - Able");
    }
}

输出:---->> B - Able"

或者我们可以使用多态。这将告诉编译器始终使用覆盖的函数:

class AAble : IAble
{
    public virtual void f()
    {
        Debug.Log("---->> A - Able");
    }
}

class BAble : AAble, IAble
{
    public override void f()
    {
        Console.WriteLine("---->> B - Able");
    }
}