为什么在实现接口方法时允许虚拟?

时间:2010-12-17 12:29:24

标签: c# interface methods virtual

我对接口有一个特定的查询。默认情况下,接口方法是抽象的和虚拟的,所以如果我们实现该接口并在类中给出定义,我们实际上会覆盖该方法但是当我们在实现类中再次将该方法标记为虚拟时,为什么编译器不考虑我们实际上在尝试隐藏原始接口虚拟方法。如果我们在基类中有一个虚方法,并且派生类再次将该方法标记为虚拟,那么编译器会发出警告您正在隐藏基类方法,因此如果您故意使用new隐藏基类方法。

public interface ITestInterface
{
 void virtualmethod(); // this method is by default virtual. 
}

public class TestInterface :ITestInterface
{
 public virtual void virtualmethod()
 {
// Now compiler should consider that i am actually hiding the interface virtual method.
 }
}

如果您为接口构建上述代码并在ILDASM中打开,您将看到如下代码: .method public hidebysig newslot abstract virtual instance void virtualmethod() cil managed { }//end of method ITestInterface::virtualmethod

3 个答案:

答案 0 :(得分:45)

默认情况下,从接口实现的方法虚拟。您只是提供了接口定义中定义的合同的实现。通过将方法标记为virtual,您允许派生类提供额外的或单独的实现,同时仍然按照定义遵守合同。

考虑这个例子:

interface IAnimal
{
    string Speak();
}

class Dog : IAnimal
{
    public string Speak()
    {
        return "Bark!";
    }
}

Dog类通过提供合同IAnimal的实现来实现接口。这里没有虚拟方法,也没有覆盖。

现在考虑这个例子:

interface IAnimal
{
    string Speak();
}

class Dog : IAnimal
{
    public virtual string Speak()
    {
        return "Bark!";
    }
}

class GoldenRetriever : Dog
{
    public override string Speak()
    {
        return "I am a golden retriever who says " 
                   + base.Speak();
    }
}

现在Dog类声明Speakvirtual,允许派生类提供额外的或新的实现。这不会破坏与IAnimal的合同,因为对Speak方法的任何调用仍会返回字符串。

好的,最后一个例子。请记住,接口不需要实现 - 它们只需要满足合同。这意味着接口只关心具有匹配签名的实现类中是否存在成员。这意味着我们也可以这样做:

interface IAnimal
{
    string Speak();
}

abstract class Dog : IAnimal
{
    public abstract string Speak();
}

class GoldenRetriever : Dog
{
    public override string Speak()
    {
        return "I am a golden retriever";
    }
}

现在请注意,Dog类根本不提供Speak的实现,但已满足合同的要求。

接口也从类继承到类,因此在上面的所有示例中,DogGoldenRetriever都实现了IAnimal接口。这两个类隐藏 Speak方法 - 两个类实现它。


好吧,我认为你的困惑可能来自虚拟方法是在接口而不是类中定义的事实。以下是我在上面定义的接口的IL:

.class private interface abstract auto ansi IAnimal
{
    .method public hidebysig newslot abstract 
        virtual instance string Speak() cil managed
    {
    }
}

虽然您确定该方法定义为virtual,但您还需要注意此处的类型被指定为interface。这纯粹是Microsoft的C#编译器生成的MSIL的实现细节 - 另一个编译器可以轻松地生成不同的代码,只要在语义上它提供相同的结果。

这里重要的是:即使该方法在接口中声明为virtual,也不意味着它与类中声明的virtual方法相同。

答案 1 :(得分:3)

接口不是基类,因此不会覆盖实现方法。接口只声明方法,接口方法默认不是虚拟的,infact接口只声明实现该接口的类上可用的方法。

声明不能是虚拟的。

实施可以是也可以不是虚拟的,完全取决于实施者的逻辑。

答案 2 :(得分:0)

在IL和C#之间,这里有“虚拟”一词的混用。 它们并不完全对应。 IL中的虚拟意味着通过VMT(虚拟方法表)“被称为间接”,这与用于类重写和接口实现的机制大致相同。 在IL的意义上,必须将接口成员标记为虚拟-无法解决。 但是,如果您查看实现,则会将其标记为“虚拟最终”。那是用C#无法实现的。 “最终”表示不能覆盖。除非您在C#中手动将其声明为“虚拟”或“抽象”,否则它不会变成C#的虚拟。

C#的隐式接口实现(在VB.NET或IL中不存在)非常强大。它确实附加了实现类的Method,该类与Name-Parameters-ReturnValue(IL措辞中的Signature或SigAndName)匹配。 这包括对方法使用基类实现。

public interface ITest
{
    double MethodC(double a, double b, double c, double d);
}

internal class BaseClass
{
    public double MethodC(double a, double b, double c, double d)
    {
        return a+b+c+d;
    }
}

internal class OtherClass : BaseClass , ITest 
{
}

这实际上工作正常。 但是C#在这里做了一个技巧,因为您使用BaseClass.MethodC作为接口实现,在BaseClass中将其标记为最终虚拟。 是的,BaseClass的实现方式取决于BaseClass的使用方式。 BaseClass.MethodC被修改,因为它用于在派生类中实现ITest.MethodC。只要BaseClass的源代码在同一解决方案中,它甚至可以在文件和项目的边界上工作。

因此,如果您单独编译项目,或者与其他产品一起在大型解决方案中进行编译,则项目编译的输出将有所不同。这很安静。

如果BaseClass的源代码不可用,如果您只是链接到DLL,则C#将生成一个包装器以使用BaseClass实现。 它实际上会这样做:

internal class OtherClass : BaseClass , ITest 
{
    double ITest.MethodC(double a, double b, double c, double d)
    {
        return base.MethodC(a, b, c, d)
    }
}

这是秘密完成的,但是绝对有必要在IL的意义上将方法标记为虚方法。