【问题标题】:C# - Explicit Interfaces with inheritance?C# - 具有继承的显式接口?
【发布时间】:2010-12-23 19:30:39
【问题描述】:

输出:
B->你好!从显式。

不应该是:?
A->你好!从显式。

为什么不从类 A 显式转换 (IHello) 调用 IHello.Hello()?

interface IHello
{
    void Hello();
}

class A : IHello
{

    public virtual void Hello()
    {
        Console.WriteLine("A->Hello!");
    }

    void IHello.Hello()
    {
        Console.WriteLine("A->Hello! from Explicit.");
    }
}

class B : A, IHello
{
    public override void Hello()
    {
        Console.WriteLine("B->Hello!");
    }

    void IHello.Hello()
    {
        Console.WriteLine("B->Hello! from Explicit.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        A a = new B();
        ((IHello)a).Hello();
    }
}

【问题讨论】:

    标签: c# inheritance explicit-interface


    【解决方案1】:

    不,不应该。

    Hello 的调用等同于被注释掉的调用——获取IHello 的路径无关紧要(除非它需要执行时检查或转换);无论哪种方式,编译时类型都只是IHello,并且接口映射是相同的,但是你到达那里。

    当一个接口在类型层次结构中多次显式实现时,将使用派生最多的类型中的实现。 (通过接口调用时。)

    来自 C# 3.0 规范的第 13.4.4 节:

    类的接口映射或 struct C 找到一个实现 每个接口的每个成员 在 C 的基类列表中指定。 具体实施 接口成员 I.M,其中 I 是 成员 M 所在的接口 声明,通过检查确定 每个类或结构 S,以 C 并为每个连续的重复 C的基类,直到匹配 位于:

    • 如果 S 包含声明 显式接口成员的 匹配 I 和 M 的实现, 那么这个成员就是实现 的 I.M.
    • 否则,如果S包含一个匹配M的非静态公共成员的声明,那么这个成员就是IM的实现如果有多个成员匹配,不指定哪个成员是IM的实现这种情况只能如果 S 是构造类型,其中在泛型类型中声明的两个成员具有不同的签名,但类型参数使它们的签名相同,则会发生这种情况。

    【讨论】:

    • +1。从较低级别的角度来看,在 B 类的 vtable 中有一个用于 IHello.Hello 的方法槽,将在其中查找实现。
    • 感谢您的及时回复!我虽然根据 OOP 规则,将基类的引用指向派生类的对象,但会掩盖基类中不存在的派生类的字段和方法。那么这是该规则的例外吗?
    • @Naximus 类型系统总是准确地知道对象的类型。将B 实例分配给A 字段只会说“让 我的代码访问此字段引用的实例,就像它是A 类型一样”。稍后将该字段转换为IHello 表示“暂时让我的代码访问此字段引用的实例,就好像它是IHello 类型一样”。引用的实例仍然是并且将始终是B 类型。仅此一项就决定了结果。只有在为实例定义了强制转换运算符时,此类操作才会对实例执行除“更改视角”之外的任何操作。
    • (cont...) 做一个非常抽象的半正确的视觉几何隐喻:将对象视为多维的,您的代码使用能够查看对象的查看器查看对象对象通过所有它的不同维度。该对象看起来非常不同,具体取决于您的代码从哪个维度查看它(例如tesseract),但它仍然是同一个对象,并且赋值和强制转换运算符只会改变查看者查看对象的维度。 ...但也许这种解释抽象而无用?我不知道。
    【解决方案2】:

    不,当您创建 new 方法 (ToString) 时,它不是虚拟的。新只是意味着您不介意它“隐藏”基类中的版本 - 因此,如果您使用已转换为特定类型 (A) 的引用调用它,它会执行类 A 中的方法,而不管您正在调用的对象的实际类型。 (即你调用了 A.ToString() 所以它执行了 A.ToString())

    当你创建一个 virtual 方法时,无论你将引用转换为什么类型,都会使用来自对象实际类型的实现(即你创建了一个 B,所以当你调用(无论对象是什么)。你好,它叫B.Hello)

    关键的区别在于一个呼叫是虚拟的,而另一个不是。

    【讨论】:

    【解决方案3】:

    (A)a 什么都不做。引用已被声明为 a,因此将其转换为 A 将无效。

    即使您的引用被声明为 A,它所引用的对象也是 B 类型。如果您将此对象强制转换为 IHello,对 Hello() 的调用将调用对象 B 对 Hello 的显式实现。

    输出完全符合预期。

    【讨论】:

    【解决方案4】:

    不,不应该。

    当您调用虚方法时,引用的类型无关紧要。调用的方法是由对象的实际类型决定的,而不是引用的类型。

    当您创建类B 的实例时,对象的实际类型是B。它打印"This is class A." 的原因是您没有覆盖B 类中的ToString 方法,而是使用new 关键字隐藏了它。因此B 类有两个ToString 方法,一个继承自A 类,一个隐藏它。如果您使用A 引用来调用ToString 方法,则会调用继承的方法,但如果您使用B 引用来调用它,则会调用遮蔽方法,并打印出"This is class B."

    此外,如果您覆盖 B 类中的 ToString 方法而不是隐藏它,则无论引用的类型如何,它都会打印出 "This is class B."

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-17
    • 1970-01-01
    • 2011-03-24
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    相关资源
    最近更新 更多