对被覆盖的接口实现的虚拟调用

本文关键字:虚拟 调用 实现 接口 覆盖 | 更新日期: 2023-09-27 18:37:04

如果我有两个类既实现接口又继承,我是否需要使函数成为虚拟函数? 例如给定:

interface IDoSomething
{
    void DoSomething();
}
class A : IDoSomething
{
    public void DoSomething()
    {
        //do A
    }
}
class B : A
{
    public new void DoSomething()
    {
        //do B
    }
}

以下代码会执行 A 还是 B?

IDoSomething doer = new B();
doer.DoSomething(); //do A or do B?

我感到困惑,因为我的印象是所有 inteface 调用都是有效的虚拟的,但显然我正在使用 new 运算符来隐藏基本定义。

对被覆盖的接口实现的虚拟调用

这是解释。已经在stackoverflow论坛上可用。

引用 Jeffrey Ritcher 来自 CLR 通过 CSharp 第 3 版在这里

CLR 要求将接口方法标记为虚拟。如果你 不要在源代码中将该方法显式标记为虚拟,即 编译器将方法标记为虚拟和密封;这可以防止 从重写接口方法派生类。如果您明确 将方法标记为虚拟,编译器将方法标记为虚拟 (并使其未密封);这允许派生类重写 接口方法。如果接口方法被密封,则派生类 无法重写该方法。但是,派生类可以重新继承 相同的接口,并且可以为 接口的方法。

class A : IDoSomething
{
    public virtual void DoSomething()
    {
        //do A
    }
}
class B : A
{
    public override void DoSomething()
    {
        //do B
    }
}

我更喜欢leppie的解决方案。 如果这不是一个选项:

class A : IDoSomething
{
    void IDoSomething.DoSomething()
    {
        //do A
    }
}
class B : A
{
    void IDoSomething.DoSomething()
    {
        //do B
    }
}

但请注意,这将隐藏实现,因此您无法执行((A)doer).DoSomething()

如果您无法将 A 类更改为这些解决方案中的任何一种,我认为没有一种确定的方法可以在所有情况下覆盖它。 您既可以显式实现接口,也可以在 B 上创建一个public new方法。 这样,如果它静态地称为IDoSomethingB它将使用B的实现,但如果它被称为A它仍将使用A的实现。

尽管 C# 和 .net 允许派生类重新实现接口方法,但在派生类

可能希望扩充(而不是完全替换)基类实现的任何情况下,通常最好让基类使用虚拟方法来实现接口,并让派生类重写该方法。 在某些语言(如 vb.net)中,无论类是否公开与正在实现的接口成员具有相同名称和签名的公共成员,都可以直接执行此操作。 在 C# 等其他语言中,实现接口的公共方法可以标记为未密封和虚拟(允许派生类重写它并让该重写调用base.Member(params)但显式接口实现不能。 在这样的语言中,最好的事情是这样的:

类 我的类 : 我的界面{  void MyInterface.DoSomething(int param)  {    做某事(参数);  }  受保护的虚拟虚空做某事(int param)  {    ...  }}类 我的类2 : 我的类{  受保护的覆盖 void doSomething(int param)  {    ...    base.doSomething(param);    ...  }}

在某些情况下,让接口实现包装虚拟调用可能是有利的,因为它允许基类确保某些事情发生在重写函数之前或之后。 例如,Dispose 的非虚拟接口实现可以包装虚拟 Dispose 方法:

  int DisposingFlag;System.Boolean 不适用于 Interlocked.Exchange  void IDisposable.Dispose()  {    if (Threading.Interlocked.CompareExchange(DisposingFlag, 1, 0) == 0)    {      处置(真);      处置标志 = 2;      Threading.Thread.MemoryBarrier();      气相色谱。抑制完成(此);    }  }  public bool Dispose { get {return (DisposingFlag != 0);} }  public bool FullDispose { get {return (DisposingFlag> 1);} }

这将(与Microsoft的默认包装器不同)确保Dispose只被调用一次,即使多个线程尝试同时调用它。 此外,它还提供了Disposed属性。 使用 Microsoft 的包装器,每个需要 Disposed 标志的派生类都必须定义自己的包装;即使基类 Disposed 标志受到保护或公开,使用起来也不安全,因为它在派生类开始清理之前不会设置。 在包装器中设置DisposingFlag可以避免该问题。