C#中多个强制转换的规则是什么?

时间:2010-02-10 10:52:42

标签: c# casting

我有这段代码片段,我想知道为什么输出如下面的评论所示:

interface I {   
    void m1();
    void m2();      
    void m3();      
}

class A : I {
    public void m1() { Console.WriteLine("A.m1()"); }
        public virtual void m2() { Console.WriteLine("A.m2()"); }
        public virtual void m3() { Console.WriteLine("A.m3()"); }
}

class C : A, I {
    public new void m1() { Console.WriteLine("C.m1()"); }
    public override void m2() { Console.WriteLine("C.m2()"); }
    public new void m3() { Console.WriteLine("C.m3()"); }
}

------
C c = new C();

((I) ((A) c)).m1();  //"C.m1()"
((I) ((A) c)).m2();  //"C.m2()"
((I) ((A) c)).m3();  //"C.m3()"

输出的原始猜测是:

A.m1();
C.m2();
A.m3();

4 个答案:

答案 0 :(得分:4)

将C级改为:

class C : A {
    public new void m1() { Console.WriteLine("C.m1()"); }
    public override void m2() { Console.WriteLine("C.m2()"); }
    public new void m3() { Console.WriteLine("C.m3()"); }
}

Jeffrey Richter的解释:

C#编译器要求将实现接口的方法标记为public。该 CLR要求将接口方法标记为虚拟。如果您没有明确标记 在源代码中作为虚拟的方法,编译器将该方法标记为虚拟和 密封;这可以防止派生类重写接口方法。如果你明确 将该方法标记为虚拟,编译器将该方法标记为虚拟(并将其保留为虚拟方法) 启封);这允许派生类覆盖接口方法。

如果密封了接口方法,派生类不能覆盖该方法。但是,一个 派生类可以重新继承相同的接口,并可以为其提供自己的实现 接口的方法。在对象上调用接口的方法时,实现 与对象的类型相关联的是。

因为C类也实现了I接口,比我们在C对象上调用接口方法时,我们将调用实现与对象类型相关的实现(即C类方法)而不是从C的基类派生的方法(不是A类)方法)。

答案 1 :(得分:1)

所有这些类型转换都是多余的,我很确定它们会被编译器优化掉。

这就是我的意思。由于(A) c CA是多余的,所以我们只剩下(I)c,这也是多余的,因为{{1}实现C。因此,我们只有一个I类的实例,编译器将为其应用正常的解析规则。

修改

原来,我完全错了。 This文档描述了会发生什么:

  

类或结构C的接口映射定位C基类列表中指定的每个接口的每个成员的实现。特定接口成员C的实现,其中I.M是声明成员I的接口,通过检查每个类或结构M来确定,从S并重复每个连续的C基类,直到匹配为止:

     
      
  • 如果C包含与SI匹配的显式接口成员实现的声明,则此成员是M的实现。
  •   
  • 否则,如果I.M包含与S匹配的非静态公共成员的声明,则此成员是M的实现。
  •   

答案 2 :(得分:1)

我认为你误解了强制转换操作符对参考转换的作用。

假设您在堆栈上引用了C实例。那是一组特定的比特。你将堆栈中的东西转换为A.这些位是否改变了?没有。没有变化。它与同一个对象的引用相同。现在你把它投射到我这次改变了吗?不。相同位。相同的参考。相同的对象。

通过像这样的强制转换的隐式引用转换只是告诉编译器在编译时确定要调用的方法时使用不同的规则。

因此,对“A”的强制转换完全不相关并被编译器忽略。所有编译器都知道或关心的是你有一个类型为I的表达式,并且你正在调用一个方法。编译器生成一个调用,说“在运行时,查看堆栈上的对象引用,并调用对象的”I.m1“槽中的任何内容。

想出这些东西的方法是考虑插槽。每个接口和类都定义了一定数量的“槽”。在运行时,类的每个实例都使用方法引用填充那些插槽。编译器生成的代码“调用该对象的插槽3中的任何内容”,这就是运行时的作用 - 在插槽中查找,调用那里的内容。

在您的示例中,有各种插槽。接口需要三个插槽,基类提供更多,派生类的“新”方法提供两个。当构造派生类的实例时,填充所有这些槽,并且可理解地,与I相关联的槽用派生类的匹配成员填充。

这有意义吗?

答案 3 :(得分:0)

投射无关紧要,因为C已包含A和I.

for((I)((A)c))。m1()和((I)((A)c))。m3():
- “new”关键字隐藏A. m1实现(实际上没有new关键字你会收到警告)。因此选择C.M1和C.M3。

对于((I)((A)c))。m2():
- 覆盖会注意选择C.M2实现。