对重写的接口实现进行虚拟调用

时间:2012-06-12 11:27:15

标签: c# interface virtual

如果我有两个实现接口但又继承的类,我是否需要将该功能设为虚拟?例如:

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运算符来隐藏基本定义。

4 个答案:

答案 0 :(得分:4)

Here就是解释。已经可以在stackoverflow论坛上找到。

  

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

     

CLR要求将接口方法标记为虚拟。如果你   不要在源代码中将方法明确标记为虚拟,   编译器将该方法标记为虚拟和密封;这可以防止   派生类来覆盖接口方法。如果你明确   将该方法标记为虚拟,编译器将该方法标记为虚拟   (并将其保密);这允许派生类重写   界面方法。如果接口方法被密封,则派生类   无法覆盖该方法。但是,派生类可以重新继承   相同的接口,可以提供自己的实现   界面的方法。

答案 1 :(得分:2)

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

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

答案 2 :(得分:0)

我更喜欢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 :(得分:0)

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

class MyClass : MyInterface
{
  void MyInterface.DoSomething(int param)
  {
    doSomething(param);
  }
  protected virtual void doSomething(int param)
  {
    ...
  }
}
class MyClass2 : MyClass
{
  protected override void doSomething(int param)
  {
    ...
    base.doSomething(param);
    ...
  }
}

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

  int DisposingFlag; // System.Boolean doesn't work with Interlocked.Exchange
  void IDisposable.Dispose()
  {
    if (Threading.Interlocked.CompareExchange(DisposingFlag, 1, 0) == 0)
    {
      Dispose(true);
      DisposingFlag = 2;
      Threading.Thread.MemoryBarrier();
      GC.SuppressFinalize(this);
    }
  }
  public bool Disposed { get {return (DisposingFlag != 0);} }
  public bool FullyDisposed { get {return (DisposingFlag > 1);} }

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