为什么一个C#类可以同时隐式和显式地继承一个接口?

16

今天我发现一个C#类可以以隐式和显式的方式继承一个接口,这使我感到惊讶。如果C#以这种方式运行,那么一个实例在不同的引用方式下可能会表现出不同的行为。

interface IFoo
{
    void DoSomething();
}

class Foo : IFoo
{
    #region IFoo Members
    public void DoSomething()
    {
        Console.WriteLine("do something implicitly");
    }
    #endregion

    #region IFoo Members
    void IFoo.DoSomething()
    {
        Console.WriteLine("do something explicitly");
    }
    #endregion
}


        Foo f = new Foo();
        f.DoSomething();

        ((IFoo)f).DoSomething();

以上代码运行并输出

do something implicitly
do something explicitly

我认为C#的这种设计导致了不一致的行为。也许强制要求一个C#类只能以隐式或显式的方式从一个接口继承,而不能同时继承。

C#被设计为这样的原因是什么?


我之前没有注意到你可以两者兼备。这似乎有点傻瓜... - Omar Kooheji
这不是愚蠢的。阅读答案。 - Mark Cidade
很抱歉我多次打了标签,我试图添加“显式接口成员实现”作为一个标签(这是您正在使用的功能名称),但显然该标签太长了。 :) - Rytmis
5个回答

13

每个实现接口的类都有一个该类成员和接口成员之间的映射关系。如果类显式实现接口成员,则显式实现将始终映射到接口。如果没有显式实现,则预期存在隐式实现,并且该隐式实现将映射到接口。

当一个类具有与接口相同的成员名称和相关类型,但是它还明确实现了接口对应的成员,则该类的“隐式”实现根本不被认为是接口的实现(除非显式实现调用它)。

除了在类实现了多个具有相同成员名称/类型的接口的每种情况中具有不同的含义外,即使只有一个接口,类本身也被认为具有隐式接口,该接口可能具有与唯一接口相同的成员/类型,但仍表示不同的含义。


这个隐式接口是一个实际的接口,还是类暴露出来的接口,换句话说,它是一种类型,尽管是隐藏的吗? - ProfK
我只是参考类公开的内容。 CLR 不区分类的实现和其固有接口(即,除非接口是单独定义的接口类型,否则无法实现另一个类的接口并替代它)。 - Mark Cidade

11

你的示例没有同时隐式和显式实现IFoo接口。你仅显式实现了IFoo.DoSomething()方法而已。你的类中有一个名为DoSomething()的新方法,与IFoo.DoSomething没有任何关系,只是它们具有相同的名称和参数。


它将如何隐式实现? - Leandro López
所以,“C#类可以同时以隐式和显式的方式继承一个接口”实际上是一种幻觉。事实上,一个类只能继承一个接口一次。 - Morgan Cheng
是的,请查看我的有关接口映射的答案:https://dev59.com/fnVC5IYBdhLWcg3wjx_u - Mark Cidade

7

这使得当发生冲突时更加灵活。特别是要看 IEnumeratorIEnumerator<T> - 它们都有一个Current属性,但类型不同。你必须使用显式接口实现来同时实现它们(并且泛型形式扩展了非泛型形式)。


如果我没记错,这是Jeff Richter在《CLR via C#》中使用的EIMIs精彩示例。 - Quibblesome
这可能是最常见的一个,因为它出现的频率非常高 :) - Jon Skeet
我在这种情况下也遇到了“this[string]”属性。我有一个带有字符串索引器的类,然后想要实现IDataErrorInfo。同样的问题-我必须显式地实现字符串索引器以进行错误处理。 - Matt Hamilton
this[T] 只是类的默认属性的语法糖(在 C# 中默认为 "Items",除非使用 DefaultPropertyAttribute)。 - Mark Cidade

2
多重继承: 如果您从定义了同一方法但用于不同目的的两个接口进行派生,会发生什么?
  interface IMoveable
  {
    public void Act();
  }

  interface IRollable
  {
    public void Act();
  }

  class Thing : IMoveable, IRollable
  {
    //TODO Roll/Move code here

    void IRollable.Act()
    {
      Roll();
    }

    void IMoveable.Act()
    {
      Move();
    }
  }

0

大家好,感谢你们的回答。

事实证明,“C#类可以同时以隐式和显式的方式继承一个接口”实际上是一种错觉。实际上,一个类只能继承一个接口一次。

在原始问题中,“DoSomething”方法似乎“隐式实现”了接口IFoo(该方法实际上是由VS2008生成的),但实际上并非如此。通过显式实现接口IFoo,"DoSomething"方法变成了普通方法,除了具有相同的签名外,与IFoo没有任何关系。

我仍然认为这是C#的一个棘手设计,很容易被误用。比如,我有一些像这样的代码

        Foo f = new Foo();
        f.DoSomething();

现在,我想将它重构为以下代码。看起来完全没问题,但执行结果却不同。

        Action<IFoo> func = foo => foo.DoSomething();
        func(f);

1
这很棘手,因为人们会认为Foo和BaseFoo一样是IFoo,但实际上并不是!更好的方式是将Foo视为扮演IFoo的角色,当您以IFoo的身份访问Foo时,您正在改变它的角色,并且可能也会改变其行为 - Mark Cidade
我认为Foo类的这种设计是有些棘手的,因为它使用了两个具有相同名称和参数的方法。你可以使用另一个例子来混淆人们的思维,那就是使用new关键字作为方法修饰符。虽然我从不使用它,因为它很容易让人感到困惑,但有一天也许我会被迫使用它。 - Hallgrim

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