有没有C#相当于Java的@Override?

时间:2013-12-31 13:52:10

标签: c# interface override abstract

以前曾经问过这个问题,但是我无法清楚地回答这个问题,这就是为什么我再问一次......

让我们使用两个例子:

  1. 类实现接口
  2. 类扩展了一个抽象类
  3. 我的感觉是,对于override关键字,两个样本的行为必须相同。 override的目标是什么?防止在超类或接口中删除方法而不在所有子类中更改或实现类。所以编译时代码一致性检查。

    在这个C#代码中,编译错误结果:' .... RepositoryContext.getXmlDoc()':找不到合适的方法来覆盖:

    interface IRepositoryContext
    {
        XmlDocument getXmlDoc();
    }
    
    class RepositoryContext : IRepositoryContext
    {
         private readonly XmlDocument gXmlDoc = new XmlDocument();
    
         public override XmlDocument getXmlDoc() // does not compile
         {    
            return gXmlDoc;
         }
    }
    

    在这个C#代码中,编译没有任何错误或警告:

    abstract class RepositoryContextBase
    {
    
         public abstract XmlDocument getXmlDoc();
    }
    
    class RepositoryContext : RepositoryContextBase
    {
         private readonly XmlDocument gXmlDoc = new XmlDocument();
    
         public override XmlDocument getXmlDoc()
         {    
            return gXmlDoc;
         }
    }
    

    这是一个有效的假设,这不应该是相同的,或者有解决方法,或者......?

6 个答案:

答案 0 :(得分:3)

override修饰符因此被定义:

  

覆盖修饰符是扩展或修改继承的方法,属性,索引器或事件的抽象或虚拟实现所必需的。

http://msdn.microsoft.com/en-us/library/ebca9ah3.aspx

override关键字指定该方法覆盖现有方法实现,这就是为什么在直接实现接口时不需要指定它的原因 - 没有这样的方法来覆盖;你是第一个实施它的人。

当您使用override关键字时,您实际上是在说“对于此类,请调用此方法而不是基本方法”。当没有这样的基本方法时(例如,当您直接实现接口时),这显然不适用。

答案 1 :(得分:2)

对于类中的虚拟或抽象方法,您需要插入override关键字,否则根本不起作用。

对于接口,没有等价物。

但是,接口实现必须实现所有基本方法,因此忘记方法通常会给您带来编译错误 这使它变得不那么重要了。

答案 2 :(得分:1)

在第一个例子中,它是您正在实施的界面。当你是继承链中唯一的实现者时,你不能override

在第二个示例中,您继承了一个具体的实现,并表示您希望实现abstract成员及其语法(尽管不是字面上的覆盖实现)是override关键字。但是,您实际上是覆盖您所属的链条,因为您正在实施

因此,考虑到覆盖关键字更多与您确保在实例上调用实现时调用而不是基类的事实有关。继承人。

这也解释了为什么你必须base.Member()内明确调用 override,因为你覆盖链。

要记住的另一个OO概念是,不是 abstractvirtual的方法可以实现相同的效果。实际上,成员可以隐藏,而您 无法使用new关键字指定。

据说这应该有助于抽象出这些非常只是语言功能或者更好地说它只是语法。

答案 3 :(得分:0)

在您的第一个示例中,您正在实现一个界面。在这种情况下,您无需指定override关键字,只需将其删除即可。

答案 4 :(得分:0)

好像你对接口实现与继承有误解。

  • 接口实现与继承完全不同。使用接口,您静态地(即在编译时)强制存在某些方法签名。因此,在这样的上下文中,诸如覆盖之类的任何关键字都是完全错误的。
  • 相反,继承通过虚方法表(基本上是方法地址列表)导致运行时多态性。

您也可以看到这一点,在C#中,您可以实现任意数量的接口,而禁止多重继承。

答案 5 :(得分:0)

原因是实现接口覆盖方法之间存在根本区别。

为了完全实现接口,您必须提供所有方法和/或属性的实现,但这些实现不一定必须依次重写。编译器希望您在创建方法时非常具体地了解您的意图,因为您可能会考虑一系列行为,并希望确定您的意思。

override关键字表示“我正在用这个覆盖基类”实现“。如果在实现接口时没有基本实现,那么它就不适用。您使用virtual表示没有基本实现的可覆盖方法,否则忽略overridevirtual

所以给出了这个界面:

interface IFoo
{
    void Bar();
}

这个类实现了该接口,并允许类依次从它继承并覆盖该实现(因为与Java不同,C#中的方法默认不是虚拟的):

class Foo : IFoo
{
    public virtual void Bar() { ... } // compiles
}

class DerivedFoo : Foo
{
    public override void Bar() { ... } // compiles, and may choose to call base.Bar()
}

此类实现该接口,但不允许覆盖:

class Foo : IFoo
{
    public void Bar(); // compiles
}

class DerivedFoo : Foo
{
    public override void Bar() { ... } // does NOT compile; Foo.Bar() is not virtual (overrideable)
}

实际上有更多的可能性,包括:

  • 您可以创建一个实现接口的abstract基类,但只为部分/全部方法提供抽象实现。
  • 您可以explicitly实施接口方法
  • 您可以seal一个重写方法来阻止进一步覆盖
  • 您可以创建一个名称相同的new method,该名称与该名称的基类方法无关

more details on MSDN

如果您对编译器不够具体,它会发出警告或抛出错误。

<强>更新

编译器在上面第二个例子中抱怨的原因是你不会得到多态行为。也就是说,如果某人引用Foo并调用Bar(),他们将获得Foo的实施,而不是DerivedFoo。这是因为Bar.Foo不在虚方法表中。换句话说,在C#中,与Java相比,默认情况下所有方法都是final,除非你另有说法。

在您的评论中,您似乎在尝试收到警告或错误,在上面的第一个示例中,您通过完全删除IFoo方法来更改Bar。 (显然,如果你只是改变方法签名,你会得到一个合适的编译错误。)

您可以通过explicitly实现此方法来实现此目的:

class Foo : IFoo
{
    void IFoo.Bar() { ... }
}

然后,如果界面发生变化,您将收到编译错误。但是,这意味着派生类不能再覆盖Foo的实现;如果你也想要这种行为,你需要:

class Foo : IFoo
{
    void IFoo.Bar() { ... }

    protected /* or public */ virtual void Bar()
    {
        IFoo foo = this; // declare rather than cast, to get compile error not runtime exception
        foo.Bar();
    }
}

如果从显式和其他实现中删除方法,您仍会遇到编译错误。

请记住,显式实现仅适用于引用IFoo而不是Foo的调用者。但是,如果在上面的代码中你添加了一个公共方法,例如,委托给显式的IFoo实现,这不会是一个问题(除非你想要它可以覆盖,否则它不必是虚拟的)。 / p>

这是一种有效的方法;是否过度杀戮是一个品味的问题,但我可以看到删除冗余代码作为重构的一部分的优点,只要这些类不公开和/或不在程序集之外使用。但是,不是以这种方式分解代码,我建议使用ReSharper这样的工具来警告你未使用的方法。