C# 隐藏方法时的接口继承传播

C# 隐藏方法时的接口继承传播,c#,inheritance,interface,C#,Inheritance,Interface,如果我有一个接口 public interface IPeelable { void PeelMe(); } 以及以下继承树: public class Fruit : IPeelable { public virtual void EatMe() { Console.WriteLine("I'm a fruit and I've been eaten"); } public void PeelMe() { Co

如果我有一个接口

public interface IPeelable
{
    void PeelMe();
}
以及以下继承树:

public class Fruit : IPeelable
{
    public virtual void EatMe()
    {
        Console.WriteLine("I'm a fruit and I've been eaten");
    }

    public void PeelMe()
    {
        Console.WriteLine("I'm a fruit and I've been peeled");
    }
}

public class Banana : Fruit
{
    public override void EatMe()
    {
        Console.WriteLine("I'm a banana and I've been eaten");
    }

    public new void PeelMe()
    {
        Console.WriteLine("I'm a banana and I've been peeled");
    }
}
我故意使用了new关键字而不是virtual/override,因此香蕉中的PeelMe方法将其隐藏在水果中

我无法理解为什么我打电话时:

IPeelable banana = new Banana();    
banana.PeelMe();// returns I'm a fruit and I've been peeled
在运行时存在一个隐式向上投射

基于继承范例调用的方法:

  • PeelMe上的虚拟/覆盖:调用Banana.PeelMe
  • 冗余的iPelable继承:称为Banana.PeelMe
  • 使用new:Fruit.PeelMe隐藏的PeelMe方法称为
难道香蕉不应该是iPelable,并且每次都在香蕉中调用PeelMe方法吗?

有人能解释一下幕后发生了什么吗?

您看到的行为是因为您隐藏了该方法,而不是覆盖它。重写方法会保留相同的签名,但会更改实现。隐藏方法会创建一个没有任何多态行为的全新方法

考虑以下代码:

Banana banana = new Banana();
banana.EatMe();

Fruit bananaAsFruit = banana;
bananaAsFruit.EatMe();
正如你所料,这张照片显示了我是一个香蕉,两次都被吃掉了。由于
EatMe
Fruit
中使用
virtual
关键字声明,并在
Banana
中重写,因此.NET使用对象类型来确定要调用的方法,而不考虑维护对该方法引用的变量。无论如何存储
Banana
对象,都会调用相同的方法

现在考虑一个类似的片段,调用非虚方法。这个输出是什么

Banana banana = new Banana();
banana.PeelMe();

Fruit bananaAsFruit = banana;
bananaAsFruit.PeelMe();
尽管
banana
bananaAsFruit
都包含一个
banana
的相同实例,但这两个调用产生的结果不同!这是因为如果没有
virtual
关键字,
Fruit.PeelMe
Banana.PeelMe
方法之间就没有联系——从编译器的角度看,它们的名称可能完全不同

根据两种类型之间的关系,将
香蕉
存储在
iElable
参考变量中可能会产生两种效果之一

  • 如果
    Banana
    继承自
    Fruit
    ,而Fruit又实现了
    ipelable
    ,则
    ipelable.PeelMe
    Banana.PeelMe
    之间没有联系
    Fruit
    iPelable
    接口实现
    PeelMe
    方法,并且(从编译器的角度来看),
    Banana.PeelMe
    方法与
    Fruit.PeelMe
    完全不同

  • 如果
    Banana
    声明实现
    ipelable
    ,则
    Banana.PeelMe
    将成为
    ipelable
    接口的实现方法。存储在
    ipelable
    引用中的
    Banana
    将打印
    I'm abana


对于
EatMe
方法,运行时只查看实际对象的类型,以确定调用什么-指向它的引用类型没有区别!这就是
virtual
override
与方法隐藏的不同之处。

这是一个有趣的问题,我认为答案也很有趣


对您问题的解释

你问:

为什么是输出

我是一个水果,我被剥皮了

而不是

我是一个香蕉,我被剥皮了

如果调用以下代码

IPeelable banana = new Banana();    
banana.PeelMe();// returns I'm a fruit and I've been peeled
Fruit banana = new Banana();    
banana.PeelMe();// returns I'm a fruit and I've been peeled
您有一个类Fruit和一个类Banana,但只有Fruit实现接口ipelable。因此,iPelable将实例化类Fruit的对象

基于此,您的问题与以下内容相同:

为什么是输出

我是一个水果,我被剥皮了

如果调用以下代码

IPeelable banana = new Banana();    
banana.PeelMe();// returns I'm a fruit and I've been peeled
Fruit banana = new Banana();    
banana.PeelMe();// returns I'm a fruit and I've been peeled
带着这个问题和另外一个问题:

后面发生了什么

我可以告诉你以下答案


回答

后面发生了什么

如果您调用
水果香蕉=新香蕉()将为包含这两种方法的香蕉分配内存。从水果和香蕉中提取PeelMe()的方法。如果香蕉的类型是水果,那么PeelMe()使用对来自水果的PeelMe()的引用。如果香蕉的类型是香蕉,则PeelMe()使用对香蕉中PeelMe()的引用


测试,验证我的答案

  • 如果将属性(整数)添加到类中并查看分配的内存,则结果如下:

  • 如果将相同的属性(integer)添加到两个类(使用virtual和override)并查看分配的内存,则结果如下:

在顶部的两个图像中,您可以看到,您为来自水果属性和香蕉属性的整数变量分配了内存

如果要测试它,还可以执行以下操作:

 public class Fruit : IPeelable
 {
     public int NumberOfBananas = 10;
     public virtual void EatMe()
     {
         Console.WriteLine("I'm a fruit and I've been eaten");
     }

     public void PeelMe()
     {
         Console.WriteLine("I'm a fruit and I've been peeled");
     }
 }

 public class Banana : Fruit
 {
     public new int NumberOfBananas = 5;
     public override void EatMe()
     {
         Console.WriteLine("I'm a banana and I've been eaten");
     }

     public new void PeelMe()
     {
         Console.WriteLine("I'm a banana and I've been peeled");
     }
 }

 private static void Main()
 {
     Fruit banana = new Banana();
     Console.WriteLine(banana.NumberOfBananas);          // Result: 10

     Banana realBanana = (Banana)banana;
     Console.WriteLine(realBanana.NumberOfBananas);      // Result: 5

     Fruit bananaFruit = (Fruit)realBanana;
     Console.WriteLine(bananaFruit.NumberOfBananas);      // Result: 10
}

请显示变量的定义和赋值
banana
它可以工作!我添加了声明,它不见了。这是重点。谢谢你,斯科特。@SeanStayn,你到donetfiddle的链接非常有用,我不知道这个在线工具。我已经深入研究了IL代码,香蕉类中的PeelMe方法是非虚拟的,但名称相同。方法public hidebysig instance void PeelMe()cil managed{在IL中对PeelMe的调用是:callvirt实例void Program/ipelable::PeelMe(),因为banana中的方法PeelMe是非虚拟的,它正在解析为Fruit.PeelMe()