在c#中隐式和显式实现接口有什么不同?

什么时候用隐式,什么时候用显式?

这两者之间有什么利弊吗?


微软的官方指南(来自第一版框架设计指南)指出,不建议使用显式实现,因为它会给代码带来意想不到的行为。

我认为这个准则在前ioc时代是非常有效的,当你不把东西作为接口传递的时候。

有人能谈谈这方面的问题吗?


当前回答

前面的回答解释了为什么用c#显式地实现接口可能更可取(主要是出于形式上的原因)。然而,有一种情况下显式实现是强制的:为了避免在接口是非公共的,但实现类是公共的情况下泄露封装。

// Given:
internal interface I { void M(); }

// Then explicit implementation correctly observes encapsulation of I:
// Both ((I)CExplicit).M and CExplicit.M are accessible only internally.
public class CExplicit: I { void I.M() { } }

// However, implicit implementation breaks encapsulation of I, because
// ((I)CImplicit).M is only accessible internally, while CImplicit.M is accessible publicly.
public class CImplicit: I { public void M() { } }

上述泄漏是不可避免的,因为根据c#规范,“所有接口成员隐式地具有公共访问权限”。因此,隐式实现也必须提供公共访问,即使接口本身是内部的。

c#中的隐式接口实现非常方便。在实践中,许多程序员一直在使用它,而没有进一步考虑。这在最好的情况下会导致混乱的类型表面,在最坏的情况下会导致泄漏封装。其他语言,比如f#,甚至不允许这样做。

其他回答

引用Jeffrey Richter从CLR通过c#编写的 (EIMI的意思是显式接口方法实现)

It is critically important for you to understand some ramifications that exist when using EIMIs. And because of these ramifications, you should try to avoid EIMIs as much as possible. Fortunately, generic interfaces help you avoid EIMIs quite a bit. But there may still be times when you will need to use them (such as implementing two interface methods with the same name and signature). Here are the big problems with EIMIs: There is no documentation explaining how a type specifically implements an EIMI method, and there is no Microsoft Visual Studio IntelliSense support. Value type instances are boxed when cast to an interface. An EIMI cannot be called by a derived type.

如果您使用接口引用,ANY虚链可以在任何派生类上显式地替换为EIMI,并且当这种类型的对象强制转换到接口时,您的虚链将被忽略,并调用显式实现。这根本不是多态性。

EIMIs还可以用于从基本框架接口的实现(如IEnumerable<T>)中隐藏非强类型接口成员,这样你的类就不会直接公开非强类型方法,但在语法上是正确的。

我大多数时候使用显式接口实现。以下是主要原因。

重构更安全

当改变一个接口时,最好是编译器能检查它。这对于隐式实现来说比较困难。

我想到了两种常见的情况:

Adding a function to an interface, where an existing class that implements this interface already happens to have a method with the same signature as the new one. This can lead to unexpected behavior, and has bitten me hard several times. It's difficult to "see" when debugging because that function is likely not located with the other interface methods in the file (the self-documenting issue mentioned below). Removing a function from an interface. Implicitly implemented methods will be suddenly dead code, but explicitly implemented methods will get caught by compile error. Even if the dead code is good to keep around, I want to be forced to review it and promote it.

不幸的是,c#没有一个关键字来强制我们将一个方法标记为隐式实现,这样编译器就可以做额外的检查。由于需要使用“override”和“new”,虚拟方法不存在上述任何一个问题。

注意:对于固定的或很少变化的接口(通常来自供应商的API),这不是问题。但是,对于我自己的接口,我无法预测它们何时/如何改变。

这是自我记录

如果我在一个类中看到'public bool Execute()',它将需要额外的工作来确定它是接口的一部分。有人可能会评论它,或者把它放在一组其他接口实现中,所有这些都在一个区域或分组评论下,说“实现的ITask”。当然,这只在组头不在屏幕外的情况下才有效。

'bool ITask.Execute()'是清晰且无二义性的。

清晰分离的接口实现

我认为接口比公共方法更“公共”,因为它们被精心设计为只暴露了混凝土类型的一小部分表面积。他们将类型简化为一种能力、一种行为、一组特征等等。在实现中,我认为保持这种分离是有用的。

当我浏览一个类的代码时,当我遇到显式的接口实现时,我的大脑会切换到“代码契约”模式。通常,这些实现只是简单地转发到其他方法,但有时它们会做额外的状态/参数检查,转换传入参数以更好地匹配内部需求,甚至转换为版本控制目的(即多代接口都转移到公共实现)。

(我意识到公开也是代码契约,但是接口要强大得多,特别是在接口驱动的代码库中,直接使用具体类型通常是内部代码的标志。)

相关:Jon的原因2。

等等

加上其他答案中已经提到的优势:

当需要时,根据消除歧义或需要内部接口 不鼓励“为实现编程”(Jon的原因1)

问题

生活并不全是乐趣和幸福。在某些情况下,我坚持使用隐式:

值类型,因为这将需要装箱和较低的性能。这不是一个严格的规则,取决于接口和它的使用方式。IComparable吗?隐式。IFormattable吗?可能明确。 具有经常被直接调用的方法的普通系统接口(如IDisposable.Dispose)。

此外,当您确实拥有具体类型并希望调用显式接口方法时,执行强制转换可能会很麻烦。我有两种处理方法:

添加公共对象并将接口方法转发给它们以供实现。在内部工作时,通常发生在更简单的接口上。 (我的首选方法)添加一个公共IMyInterface I {get{返回这个;}}(应该内联)并调用foo.I.InterfaceMethod()。如果有多个接口需要此功能,则将名称扩展到I以外(根据我的经验,我很少有这种需求)。

显式接口实现的一个重要用途是在需要实现具有混合可见性的接口时。

这个问题和解决方案在c#内部接口这篇文章中有很好的解释。

例如,如果您希望保护应用程序层之间对象的泄漏,该技术允许您指定可能导致泄漏的成员的不同可见性。

除了已经提供的优秀答案之外,还有一些情况需要显式实现,以便编译器能够找出需要什么。以IEnumerable<T>为例,它可能会经常出现。

这里有一个例子:

public abstract class StringList : IEnumerable<string>
{
    private string[] _list = new string[] {"foo", "bar", "baz"};

    // ...

    #region IEnumerable<string> Members
    public IEnumerator<string> GetEnumerator()
    {
        foreach (string s in _list)
        { yield return s; }
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion
}

这里,IEnumerable<string>实现了IEnumerable,因此我们也需要。但是等等,泛型和普通版本都实现了具有相同方法签名的函数(c#对此忽略了返回类型)。这是完全合法的。编译器如何解决使用哪个?它迫使您最多只有一个隐式定义,然后它就可以解决它需要解决的任何问题。

ie.

StringList sl = new StringList();

// uses the implicit definition.
IEnumerator<string> enumerableString = sl.GetEnumerator();
// same as above, only a little more explicit.
IEnumerator<string> enumerableString2 = ((IEnumerable<string>)sl).GetEnumerator();
// returns the same as above, but via the explicit definition
IEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator();

PS: IEnumerable的显式定义中有一点间接的作用,因为在函数内部,编译器知道变量的实际类型是StringList,这就是它解析函数调用的方式。实现一些抽象层,一些。net核心接口似乎已经积累起来了。

如果显式实现,则只能通过与接口类型相同的引用引用接口成员。实现类类型的引用不会公开这些接口成员。

如果实现的类不是公共的,除了用于创建类的方法(可以是工厂或IoC容器),并且除了接口方法(当然),那么我认为显式实现接口没有任何好处。

否则,显式实现接口将确保不使用对具体实现类的引用,从而允许您在以后更改该实现。“确保”,我想,是“优势”。一个分解良好的实现可以在没有显式实现的情况下完成这一点。

在我看来,缺点是您会发现自己在实现代码中对接口进行类型转换,而接口可以访问非公共成员。

就像许多事情一样,优势就是劣势(反之亦然)。显式实现接口将确保不会公开具体的类实现代码。