重写显式接口实现?

21

如何在子类中正确覆盖接口的显式实现?

public interface ITest
{
    string Speak();
}

public class ParentTest : ITest
{
    string ITest.Speak()
    {
        return "Meow";
    }
}

public class ChildTest : ParentTest
{
    // causes compile time errors
    override string ITest.Speak()
    {
        // Note: I'd also like to be able to call the base implementation
        return "Mooo" + base.Speak();
    }
}

上面是对语法的最佳猜测,但显然是错误的。它会导致以下编译时错误:

error CS0621:

`ChildTest.ITest.Speak()': 虚成员或抽象成员不能是私有的

error CS0540:

ChildTest.ITest.Speak()': 包含类型未实现接口ITest'

error CS0106:

`override' 修饰符不适用于此项

实际上,我可以在不使用显式接口的情况下使其正常工作,因此这并不会阻止我,但出于好奇,如果我想使用显式接口来完成这个操作,正确的语法是什么?


2
隐式接口实现有什么问题?你想要实现什么目标? - chomba
@chomba,在我的代码库中,使用显式接口一直是一种风格选择。我认为,查看实现4或5个不同接口的类,并知道哪些方法与哪个接口相关联,会更加清晰。对于大多数用例来说,这已经很好了,这是我第一次遇到它们的限制。 - James McMahon
据我所知,显式的主要用例是具有相同签名成员的多个接口。我很少看到它们被使用,如果我看到了,我会觉得编写代码的人并不经常使用C#。 - Casey
这涉及程序员的偏好,https://dev59.com/PnVC5IYBdhLWcg3w7V1q 上有一堆答案涵盖了各种方法的利弊。 - James McMahon
@JamesMcMahon 是的,我同意,但我觉得这是那些经常使用这种语言的人大多数都同意的事情之一。这就像我看到C#代码中写着var some_object = new my_class()会感到奇怪;虽然蛇形命名法并没有错,但在C#中却感觉不合适。 - Casey
5个回答

20

一个显式接口实现不能成为虚成员。详见C#语言规范13.4.1节(尽管该规范已经过时,但此逻辑在C# 6.0中似乎没有改变)。具体而言:

对于显式接口成员实现包含访问修饰符是编译时错误,同时使用abstract、virtual、override或static修饰符也是编译时错误。

这意味着您无法直接覆盖此成员。

作为一种解决方法,您可以从显式实现中调用另一个虚拟方法:

class Base : IBla
{
    void IBla.DoSomething()
    {
        this.DoSomethingForIBla();
    }

    protected virtual void DoSomethingForIBla()
    {
        ...
    }
}

class Derived : Base
{
    protected override void DoSomethingForIBla()
    {
        ...
    }
}

1
非常好的回答,附有文档链接。我一直将显式接口用作样式选择,没有意识到它们有这种限制。 - James McMahon
太棒了,@Bas!我一直在努力思考为什么无法覆盖我的接口实现,你的回答真的帮了我很大的忙。谢谢! - João Ignacio

15

我也有过这样的情况,我想覆盖一个显式接口实现并调用基类,但发现这个问题及其答案说“不能这样做”。

首先要注意的是,为了在不调用基类的情况下覆盖显式接口实现是相当简单的。派生类只需实现该接口即可。

public class ChildTest : ParentTest, ITest
{
  string ITest.Speak()
  {
    return "Mooo";
  }
  // Note: any other interface functions will call ParentTest's implementation
}

然而,现在没有“合法”的方法可以在类型为ChildTest的对象上调用ParentTest实现的ITest.Speak,因为任何尝试使用该接口都会导致调用ChildTest的实现。

因此,只有对基本实现的调用才会引起复杂性。 为了满足我的好奇心,我证明它是可以做到的,但真的不应该这样做...

保持基类不变,以下实际上允许使用反射调用基类。

public class ChildTest : ParentTest, ITest
{
  string ITest.Speak()
  {
    return "Mooo" + typeof(ParentTest).GetMethod("ITest.Speak", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(this, new object[0]) as string;
  }
}

如果示例代码被包含在命名空间中,则需要使用完全限定的接口名称,例如"MyNamespace.ITest.Speak"

如果该函数将被重复调用和/或用于许多对象,则可以通过缓存方法信息和/或为基本调用创建委托来提高性能,例如:

public class ChildTest : ParentTest, ITest
{
  static ChildTest()
  {
    baseSpeakMethodInfo = typeof(ParentTest).GetMethod("ITest.Speak", BindingFlags.Instance | BindingFlags.NonPublic);
  }

  static private MethodInfo baseSpeakMethodInfo;

  public ChildTest()
  {
    baseSpeak = baseSpeakMethodInfo.CreateDelegate(typeof(Func<string>), this) as Func<string>;
  }

  private Func<string> baseSpeak;

  string ITest.Speak()
  {
    return "Mooo" + baseSpeak();
  }
}
这个答案唯一的优点是:当你不能修改基类时,它可以解决问题。否则,这是一个糟糕的解决方案,应该在基类中创建机制(如其他答案中所述)来为派生类提供合法的调用基本实现方法的方式。

这个答案唯一的优点是:当你不能修改基类时,它可以解决问题。否则,这是一个糟糕的解决方案,应该在基类中创建机制(如其他答案中所述)来为派生类提供合法的调用基本实现方法的方式。


1
你可以使用一个protected virtual方法,并保持实现为非公开,这样你仍然有显式接口实现,它只是一个包装器,包装了实现:
public class ParentTest : ITest
{
    protected virtual string Speak_Impl()
    {
        return "Meow";
    }
    string ITest.Speak()
    {
        return Speak_Impl();
    }
}

public class ChildTest : ParentTest
{
    protected override string Speak_Impl()
    {
        return "Mooo";
    }
}

有趣,所以没有直接覆盖显式接口实现的方法? - James McMahon

0
在父类中使用 public virtual string Speak(),在子类中使用 public override string Speak() 应该可以正常工作。你不能在这种情况下使用显式接口。如果需要使用显式接口实现,则可以通过声明受保护的成员并在显式接口实现中调用它来解决问题。

2
那时候已经不是显式接口了。如果您尝试在显式接口前面添加虚拟关键字,您会得到“错误CS0106:此项无效的修饰符'virtual'”。 - James McMahon
@JamesMcMahon 没有理由让你的示例成为一个显式接口;如果这是一个要求,你应该改变你的示例/问题。 - Casey
更新了问题以澄清事情 - James McMahon
@JamesMcMahon 当你需要实现多个具有相同签名的接口方法时,才需要显式接口。在这种情况下,答案是创建没有这些名称的方法,并在显式接口实现中调用它们。 - Casey

0

你不能覆盖显式接口实现。它们不能是虚拟的,因此没有直接覆盖它们的方法。但是,您可以通过使其调用受保护的虚拟成员来间接地使它们成为虚拟的:

public interface ITest
{
    string Speak();
}

public class ParentTest : ITest
{
    string ITest.Speak()
    {
        return Speak();
    }

    protected virtual string Speak()
    {
        return "Meow";
    }

}

public class ChildTest : ParentTest
{
    protected override string Speak()
    {
        return "Mooo";
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接