4

我是 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时打印。为什么?编译器如何知道应该调用什么函数?

当决定调用什么函数时——运行时还是编译时?如果我玷污了一个新班级class CAble : IAble怎么办?

4

4 回答 4

2

因为AAble正在实现IAble接口,所以将其AAble.f标记IAble.f为 type 方法的实现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

接口实现virtual在 IL 中被标记为,即使它在 C# 中没有被标记为虚拟。该方法final在 IL 中也被标记为,如果该方法virtual在 C# 中,则不会被标记为final.

于 2018-09-06T12:16:33.487 回答
1

通常会有一个编译器警告,因为它隐藏了一个方法。但在 C# 中,对非虚拟函数执行此操作是合法的。但是,当然,如果它是一个虚函数,那么显然该方法的 B 版本将运行。

因为您将其声明为 IAble 并且它是非虚拟的,所以编译器将其读取为 IAble。如果它被声明为虚拟的,编译器将扫描继承的层次结构并看到它的实际类是一个 BAble 并且它将运行 BAble 代码。

于 2018-09-06T11:56:31.337 回答
1

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

这意味着当您使用基类型声明变量并使用派生类型对其进行初始化时,将使用基类中的方法。这就是你的代码中发生的事情。

更一般:当您隐藏方法时,将使用的方法的版本,来自您声明它的类的 cmo。

因此,如果您有另一个班级CAble并像这样使用:

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

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

在您的情况下,您将变量声明为IAble. 它没有实现,所以它着眼于实现,它在 class 中定义AAble。其他类只隐藏方法。

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

在定义方法时,您期望的是重写方法,通过使用overridekeywaord 来完成。

为了覆盖方法,它应该在基类中标记为virtual(如果它有实现)或abstract(如果它没有实现)。

于 2018-09-06T11:56:47.863 回答
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");
    }
}
于 2018-09-06T12:15:31.987 回答