【问题标题】:virtual calls on overridden interface implementations对覆盖接口实现的虚拟调用
【发布时间】:2012-06-15 06:41:03
【问题描述】:

如果我有两个类既实现了接口又继承了,我需要将函数设为虚拟吗?例如给定:

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?

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

【问题讨论】:

  • 问题是 A 还是 B,或者 为什么 A 而不是 B?因为第一个你可以很容易地回答自己。
  • 是的,为什么,对不起!将编辑。

标签: c# interface virtual


【解决方案1】:

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

类 MyClass : MyInterface { void MyInterface.DoSomething(int param) { 做某事(参数); } 受保护的虚拟 void doSomething(int param) { ... } } 类 MyClass2 : MyClass { 受保护的覆盖无效doSomething(int param) { ... base.doSomething(参数); ... } }

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

int DisposingFlag; // System.Boolean 不适用于 Interlocked.Exchange 无效 IDisposable.Dispose() { if (Threading.Interlocked.CompareExchange(DisposingFlag, 1, 0) == 0) { 处置(真); 处置标志 = 2; Threading.Thread.MemoryBarrier(); GC.SuppressFinalize(this); } } public bool Disposed { get {return (DisposingFlag != 0);} } public boolfullyDisposed { get {return (DisposingFlag > 1);} }

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

【讨论】:

    【解决方案2】:

    我更喜欢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 的实现。

    【讨论】:

      【解决方案3】:

      Here 是解释。已在 stackoverflow 论坛上提供。

      在此处通过 CSharp 第 3 版从 CLR 引用 Jeffrey Ritcher

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

      【讨论】:

      • 正是我正在寻找的信息,谢谢!我什至想问重新继承接口。
      【解决方案4】:
      class A : IDoSomething
      {
          public virtual void DoSomething()
          {
              //do A
          }
      }
      
      class B : A
      {
          public override void DoSomething()
          {
              //do B
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2021-11-02
        • 2014-11-20
        • 2016-09-15
        • 1970-01-01
        • 1970-01-01
        • 2010-11-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多