【问题标题】:Why doesn't C# support explicitly implemented virtual methods?为什么 C# 不支持显式实现的虚拟方法?
【发布时间】:2011-07-03 15:59:26
【问题描述】:

C# 中的接口方法可以显式实现,以便在将实例显式转换为接口类型时调用它们的实现。为什么类的虚方法也不支持这一点?

虽然解决“多重继承”问题是接口所独有的,但似乎出于其他所有原因,显式实现的成员对接口很有用,它们对虚拟方法也很有用。一个更清晰的回报型协方差模型浮现在脑海中。

编辑:根据要求,示例:

public class Foo {
    ...
}

public class Bar : Foo {
   ...
}

class Base {
   abstract Foo A ();
}

class Dervied {
  private Bar _b;

  Bar A () {
    return _b;
  }

  Foo Base.A () {
    return _b;
  }
}

我知道使用辅助方法来模拟这种情况,但最终效果似乎具有显式实现所具有的任何不良特征,但 API 更脏。我的问题的症结不是如何做返回类型协变,而是为什么虚拟方法不支持类似的接口机制。

【问题讨论】:

  • 您能解释一下您对这项工作的设想吗?如果Derived 扩展Base,您正在寻找Base 上的虚拟方法,仅在Derived 转换为Bar 的实现上调用?我看不到这种信息隐藏形式的实用程序。举个例子会有所帮助。
  • 显式接口实现是一个创可贴。您必须使用它来解决由于能够继承可能具有具有相同签名的成员的多个接口而引入的歧义。这是多重继承的标准问题,减去必须在多个继承的实现之间进行选择的问题。 C# 中不存在的问题,因为接口无法定义实现。因此不需要显式方法覆盖,只有一个基本实现可供选择。
  • 您的示例使用abstract 方法,而不是virtual 方法。你的问题能更准确一点吗?
  • 抽象方法实际上是隐式的并且更接近于接口中定义的成员,这是我的比较。然而,我的问题旨在涵盖这两种情况。

标签: c# interface virtual-functions explicit-implementation


【解决方案1】:

有些人建议首先不要使用public virtual 方法。而是创建一个代表消费者接口的public 非虚拟方法和一个代表实现者接口的protected virtual 方法。

我不会将调用者和实施者的合同分开称为“混淆设计”。在许多情况下,它更清洁 IMO,但我通常懒得实际这样做。

这种设计在返回类型协方差和方法隐藏方面效果更好。

这样做的另一个好处是public 包装器可以添加额外的检查代码并支持调用者和实现者的不同合约。

我如何模拟返回类型协方差的示例:

public class Base
{
    protected virtual Base FooOverride(int i){return new Base();};//FooOverride does not need to duplicate the argument checking

    public Base Foo(int i)
    {
        if(i<0)
          throw new ArgumentException("i<0");
        return FooOverride(i);
    }
}

public class Derived:Base
{
    protected override Base FooOverride(int i){return new Derived();};
    public new Derived Foo(int i)
    {
        return (Derived)base.Foo();
    }
}

【讨论】:

  • 这是一个非常有用的设计。我希望 .NET 将它用于他们的一些 System 库。这种设计模式有名字吗?
【解决方案2】:

除了允许这样的事情之外,还有什么好处?

class Base
{
    virtual void M() { }
}

class Derived : Base
{
    override void M() { }

    override void Base.M() { }
}

这有效地将违反 Liskov Substitution Principle 的行为写入 C# 语言 - 如果我有一个 Base 类型的变量,根据运行时类型是 Base 还是 Derived,对其调用 M() 可以做完全不同的事情.

显式接口实现不同。假设你有这个:

interface IFoo
{
    void DoStuff();   
}

interface IBar
{
    void DoStuff();
}

class C : IFoo, IBar
{
    void IFoo.DoStuff() { }

    void IBar.DoStuff() { }
}

这保留了 LSP - 如果我有一个恰好是运行时类型 C 的 IFoo 变量,则对其调用 DoStuff() 将获得它的 IFoo 实现。 IBar 也是如此。

【讨论】:

  • 一种常见的模式是返回类型协方差,如 OP 所述。添加一个新方法,它等效于 virtual 函数,但它返回更具体的类型。
  • 显式实现可用于支持返回类型协变,而不会混淆具有隐藏虚拟辅助方法的 API(它破坏主接口的方式与显式实现相同,通过使主基础和覆盖无关的)。有趣的是,返回类型协方差是您的链接提到的要求之一,但本机并不支持。
  • 假设我有一个抽象基类 ReadableFoo,它有一个抽象的只读属性 Bar。我想要一个从 ReadableFoo 派生的 MutableFoo 类,它具有读写属性 Bar。有没有办法创建这样一个类而不必添加额外的继承层,其目的是覆盖只读属性“Bar”?就此而言,是否有任何方法可以构建一个暴露 ReadableFoo 和 MutableFoo 的项目,如上所述,而不暴露任何其他类?请注意,ReadableFoo 和 MutableFoo 完全遵守 LSP。
  • @supercat:让 MutableFoo 继承自 ReadableFoo 对我来说似乎很奇怪。它感觉就像是对继承的滥用(但你绝对正确,它不会破坏 LSP)。我认为我要做的是让 ReadOnlyFoo 持有对 MutableFoo 的 private readonly 引用,并将所有属性访问委托给该实例。是的,每当您添加/删除/更改属性时,您都必须在 2 个地方进行更新,但是使用继承也会遇到同样的问题。我还会看看为什么需要一个 ReadOnlyFoo 和一个 MutableFoo - 这似乎是代码的味道。
  • @Zack Elan:同时拥有 ReadableFoo(不是 ReadOnlyFoo!)和 ImmutableFoo 的原因是一些例程希望接受他们现在可以读取的 Foo,但不会关心它是否以后可能会改变。其他人可能想要一个他们知道永远不会改变的 Foo。可以在 ReadableFoo 中具体实现只读属性,但前提是所有派生类都希望将它们的信息存储在相同的支持字段中——这种假设可能适用于某些类,但并非全部。举个简单的例子……
猜你喜欢
  • 1970-01-01
  • 2017-01-16
  • 2011-03-15
  • 2018-11-11
  • 1970-01-01
  • 1970-01-01
  • 2012-12-07
  • 2013-03-08
  • 1970-01-01
相关资源
最近更新 更多