修复了代码问题。 好的,我想我需要澄清这个问题。
new A.example();输出“A”
示例方法中应该包含哪些内容以便输出“???”? 这甚至可能吗?
public class Letter {
public virtual void AsAString() {
Console.WriteLine("???");
}
public void example() {
this.AsAString();
}
}
public class A : Letter {
public override void AsAString() { Console.WriteLine("A"); }
public void example2() { base.AsAString(); }
}
new A().example2();
new A().example();
答案 0 :(得分:2)
这很容易:
public void example() {
Console.WriteLine("???");
}
从这个答案中你应该意识到你实际要求的并不是你想要的......
如果您的意思是要将虚拟方法称为非虚拟方法,那么这是不可能的。即使您将引用转换为基类,它仍然使用对象的实际类型来确定要调用的方法。
答案 1 :(得分:2)
让我们首先确保我正确地解释您的问题。您有如上定义的类。您正在实例化A
的实例并调用example
从基类继承的方法A
。您想知道方法this.AsAString()
中的调用Letter.Example
是否可以调用AsAString
的基本实现而不是派生的实现。
首先,让我们理解为什么如上所述定义example
,通过Letter.example
的实例(例如A
)调用new A().example
会导致A.AsAString
被调用。从规范(第7.4.4节):
确定要调用的函数成员实现:
如果E的编译时类型是接口,则要调用的函数成员是由E引用的实例的运行时类型提供的M的实现。此函数成员通过应用接口映射规则来确定(§13.4.4)确定由E引用的实例的运行时类型提供的M的实现。
否则,如果M是虚函数成员,则要调用的函数成员是由E引用的实例的运行时类型提供的M的实现。此函数成员通过应用规则来确定确定M的最多派生实现(第10.6.3节)与E引用的实例的运行时类型有关。
否则,M是非虚函数成员,要调用的函数成员是M本身。
现在让我们考虑你的情况。您有一个a
的实例A
来自Letter
。您已通过语法example
调用名为a.example()
的方法。这将调用具有定义的Letter.example
:
public void example() {
this.AsAString();
}
这会调用Letter.AsAString
。但是,Letter.AsAString
已声明为virtual
,因此,根据上面的粗体规则,调用的方法为A.AsAString
,因为this
的类型为A
,{ {1}}来自A
,Letter
提供A
override
。
现在,如果您更改Letter.AsAString
的定义,以便使用A.AsAString
修饰符隐藏基础实现
new
然后public new void AsAString() {
Console.WriteLine("A");
}
将导致使用基础实现,您将根据需要看到输出a.example
。这是因为,根据上述规则,???
的派生程度最高的实现(即Letter.AsAString
层次结构中提供A
方法{{1}的定义的派生类型最多的类型}}是基础实现。 virtual
修饰符允许AsAString
使用与new
具有相同签名的A
方法,但它不是AsAString
方法。
如果我错误地解释您的问题,或者如果上述任何一项需要澄清,请告诉我。
答案 2 :(得分:1)
回答你的问题,我认为不可能。一旦覆盖该方法,除非使用基本访问器访问基类实现,否则您的调用将是您拥有的“最专业”方法(在这种情况下,在A中定义的AsASString中的方法,而不是在A中定义的AsASString)信。
Woot4Moo放在一起的建议不起作用,因为AsASString()和this.AsASString()正在访问相同的方法,恕我直言(在A中实现该方法)。
正如您所知,base.AsASString()实际上调用了基本方法,而不是专门的方法。
我希望这会有所帮助。
干杯,瓦格纳。
答案 3 :(得分:1)
C#中的virtual
关键字与大多数其他语言中的关联一样 - 具体而言,调用的具体方法由实例的实际运行时类型决定。
您正在寻找的只是简单的方法隐藏。该计划:
class A
{
public void Foo()
{
Console.WriteLine("Foo called from A");
}
public virtual void Bar()
{
Console.WriteLine("Bar called from A");
}
public virtual void Baz()
{
Console.WriteLine("Baz called from A");
}
}
class B : A
{
public new void Foo()
{
Console.WriteLine("Foo called from B");
}
public override void Bar()
{
Console.WriteLine("Bar called from B");
}
public override void Baz()
{
base.Baz();
Console.WriteLine("Baz called from B");
}
}
static void Main()
{
A a = new A();
a.Foo();
a.Bar();
a.Baz();
B b = new B();
b.Foo();
b.Bar();
b.Baz();
A a2 = new B();
a2.Foo();
a2.Bar();
a2.Baz();
}
将产生以下输出:
Foo called from A
Bar called from A
Baz called from A
Foo called from B
Bar called from B
Baz called from A
Baz called from B
Foo called from A
Bar called from B
Baz called from A
Baz called from B
让我们打破这个:
方法A.Foo()
是一种非虚拟方法,B
隐藏。如果您在声明为Foo
的变量上调用A
,则始终会调用A.Foo()
,即使它实际上是B
的实例。
方法A.Bar()
是一种虚拟方法,由B
覆盖。如果对声明为Bar
的变量调用A
,如果变量实际上是B.Foo()
的实例,则实际上会调用B
。根据规范,这就是虚拟调度的工作方式。
方法A.Baz()
也是虚拟的,但B.Baz()
在运行自己的代码之前也会调用基本版本。这就是为什么在最后两组中看到该方法的两行输出。 只有派生类可以调用虚拟基本方法 - 无法从外部调用它。
因此,如果您需要能够执行基本方法和派生方法,不使方法成为虚拟方法。在派生类中隐藏或阴影。
答案 4 :(得分:1)
您似乎对虚拟方法的工作方式感到困惑。这是一个思考它的好方法。想象一下,每个类的每个实例都有一定数量的“槽”。在运行时,插槽包含一个方法。要调用方法,请告诉运行时“调用此对象的插槽x中的任何方法”。
当你制作一个“抽象”方法时,它会创建一个新的插槽并且不会放入任何内容。
制作“虚拟”方法时,会创建一个新插槽并将方法放入插槽中。
制作“覆盖”方法时,不会创建新插槽。它通过替换先前声明的虚方法槽中的任何内容来“覆盖”。
当你制作一个“新”方法时,它会创建一个新的插槽。这就是“新”的意思。
当你制作一个没有注释的普通方法时,它就像一个“新”方法。
当您编写调用方法的代码时,编译器会计算出您正在讨论的插槽,并生成代码,告诉运行时“调用此对象的此插槽中的任何方法。”
此规则的例外是“基本”电话;生成代码意味着“忽略插槽中的内容并调用此方法的基类版本”。
现在清楚了吗?
答案 5 :(得分:0)
对此的解决方案是声明A.AsAString,如下所示:
public new void AsAString() { Console.WriteLine("A"); }
这可能不符合您的需求,因为((Letter)new A()).AsAString()
将返回“???”。如果这符合您的需求,那么您最好不要首先将其声明为虚拟。
因此,您要么允许通过基本引用调用您的覆盖函数,要么不这样做。有点你不能吃,也吃它。
考虑这个问题的另一种方法是,如果存在这样的解决方案,将会严重破坏封装。关于它如何与基类交互,该类应该是权限。对于类的用户,调用哪个基函数的确切实现应该是不透明的。 A.AsAString()
必须是与Letter.AsAString()
互动的权威,而不是其他任何人。
要实现您想要的功能,您需要添加其他功能,如下所示: 公共课信{
public virtual void AsAString() {
BaseAsAString();
}
public virtual void BaseAsAString() {
Console.WriteLine("???");
}
public void example() {
this.AsAString();
}
}
public class A : Letter {
public override void AsAString() { Console.WriteLine("A"); }
}
A a = new A();
a.AsAString(); //A
a.BaseAsAString(); //???