在C#中隐式和显式实现接口有什么区别?

什么时候应该使用隐式,什么时候应该使用显式?



Microsoft的官方指南(来自第一版Framework Design Guidelines)指出,不建议使用显式实现,因为它会使代码产生意外的行为。 br />我认为该指南在IoC之前是非常有效的,当您不将事物作为接口传递时。

评论

阅读有关C#接口的完整文章:planetofcoders.com/c-interfaces

是的,应该避免使用显式接口,并且应该采用更专业的方法来实现ISP(接口隔离原则),这是同一代码项目上的详细文章

#1 楼

隐式是当您通过类中的成员定义接口时。显式的是您在接口上的类中定义方法时。我知道这听起来令人困惑,但这就是我的意思:IList.CopyTo将隐式实现为: br />
不同之处在于,隐式实现允许您通过将接口强制转换为该类以及接口本身来通过创建的类访问接口。显式实现只允许您通过将接口强制转换为接口本身来访问该接口。 。无论如何,我很少使用它。

我相信还有更多的理由要使用/不明确使用别人会发表的文章。每个背后的推理。

评论


我知道这篇文章很旧,但是我发现它非常有用-需要注意的一件事是如果不清楚,因为在我的例子中隐式对象具有public关键字...否则,您将获得一个错误

– jharr100
2014年11月18日19:30

杰弗里·里希特(Jeffrey Richter)通过C#4 ed ch 13进行的CLR显示了一个不需要强制转换的示例:内部结构SomeValueType:IComparable {private Int32 m_x;公共SomeValueType(Int32 x){m_x = x; } public Int32 CompareTo(SomeValueType other){...);} Int32 IComparable.CompareTo(Object other){return CompareTo((SomeValueType)other); }} public static void Main(){SomeValueType v = new SomeValueType(0);对象o = new Object(); Int32 n = v.CompareTo(v); //没有装箱n = v.CompareTo(o); //编译时错误}

–安迪·登特(Andy Dent)
15年8月26日在5:18

今天,我遇到了一种罕见的情况,需要使用显式接口:一个类,该类具有由接口构建器生成的字段,该字段将字段创建为私有字段(Xamarin使用iOS Storyboard定位到iOS)。还有一个公开该字段的接口(公共只读)。我可以在接口中更改getter的名称,但是现有名称是该对象最符合逻辑的名称。因此,我做了一个明确的实现,它引用了私有字段:UISwitch IScoreRegPlayerViewCell.markerSwitch {get {return markerSwitch; }。

–ToolmakerSteve
16年7月12日在23:11



编程的恐怖。好吧,很好地揭穿了!

–液体核心
18年5月3日,11:54

@ToolmakerSteve需要显式实现至少一个接口成员的另一种情况(更常见)是实现多个接口,这些接口的成员具有相同的签名但返回类型不同。这可能是由于接口继承而发生的,就像IEnumerator .Current,IEnumerable .GetEnumerator()和ISet .Add(T)一样。这是另一个答案。

–phoog
5月20日22:13

#2 楼

隐式定义只是将接口所需的方法/属性等作为公共方法直接添加到类中。

显式定义强制成员仅在使用接口时才公开直接,而不是基础实现。在大多数情况下,这是首选方法。

通过直接使用接口,您无需确认,
并将代码耦合到底层实现。如果您的代码中已经有一个公共属性Name,并且您想要实现一个也具有
Name属性的接口,则显式地将两者分开。即使
,如果他们在做同样的事情,我仍将显式
调用委托给Name属性。您永远不会知道,您可能想要更改
名称如何在普通类中工作,以及名称如何在稍后使用接口
属性。
可能仅与
界面的客户端相关的新行为,这意味着您没有使班级简洁明了
(我认为)。

评论


您在这里提出了一些要点。尤其是A。无论如何,我通常都会将类作为接口传递给我,但是从这个角度来看,我从未真正想到过它。

– Matttlant
08-09-27在11:15

我不确定是否同意C点。Cat对象可能实现IEatable,但Eat()是事物的基本组成部分。在某些情况下,当您使用“原始”对象而不是通过IEatable接口时,只想在Cat上调用Eat(),不是吗?

–LegendLength
09年5月6日,12:50

我知道在某些地方Cat确实可以毫无异议地食用。

–温贝托
2010年6月14日下午1:37

我完全不同意以上所有观点,并且会说使用显式接口是灾难的根源,而不是按其定义定义的OOP或OOD(请参阅我关于多态性的答案)

– Valentin Kuzub
2011-2-9 18:49



另请参阅:stackoverflow.com/questions/4103300/…

–扎克·詹森(Zack Jannsen)
2012-09-18 13:13

#3 楼

除了已经提供的出色答案外,在某些情况下,还要求显式实现以使编译器能够确定所需的内容。以IEnumerable<T>为例,它很可能会经常出现。

这里是一个例子: IEnumerable<string>,因此我们也需要。但是请放心,通用版本和普通版本都以相同的方法签名实现函数(C#为此忽略返回类型)。这是完全合法的。编译器如何解析要使用的?它强制您最多只有一个隐式定义,然后它可以解析所需的任何内容。

即。

PS:IEnumerable的显式定义中的一小部分间接操作有效,因为在函数内部编译器知道变量的实际类型是StringList,这就是它解析函数调用的方式。用于实现某些.NET核心接口的某些抽象层的绝妙事实似乎已经积累。

评论


@Tassadaque:两年之后,我只能说“好问题”。我不知道,除了也许我从正在研究抽象代码的地方复制了该代码。

–马修·沙利(Matthew Scharley)
2011年6月26日22:17

@Tassadaque,您是对的,但是我认为上面的Matthew Scharley帖子的要点没有丢失。

–funkymushroom
16年7月7日在17:32

#4 楼

用C#引用CLR的Jeffrey Richter(EIMI表示显式接口方法实现)


对您来说至关重要的是
了解
的一些后果
使用EIMI时存在。并且由于这些影响,您应该尝试
尽可能避免EIMI。
幸运的是,通用接口可以帮助
避免EIMI很多。但是有时仍然需要使用
(例如,实现两个具有相同名称和签名的
接口方法)。以下是EIMI的主要问题



没有文档说明类型是如何具体实现EIMI方法的,而
则没有Microsoft Visual Studio
IntelliSense支持。
值类型实例在转换为接口时被装箱。
EIMI不能由派生类型调用。



如果使用接口引用,则可以在任何派生类上将任何虚拟链显式替换为EIMI,并且当将此类对象强制转换为接口时,将忽略您的虚拟链并调用显式实现。除多态性外,什么都没有。

EIMI还可以用于从基本Framework Interfaces的实现(例如IEnumerable )隐藏非强类型接口成员,因此您的类不会暴露非强类型方法直接,但是在语法上是正确的。

评论


尽管合法,但接口的重新实现通常最多是可疑的。显式实现通常应直接链接到虚拟方法,或通过应该在派生类上绑定的包装逻辑链接到虚拟方法。虽然可以以对适当的OOP约定不利的方式使用接口,但这并不意味着不能更好地使用它们。

–超级猫
2014年5月30日20:10



@Valentin EIMI和IEMI代表什么?

– Dzienny
2014年6月9日12:50

显式接口方法的实现

–scobi
14年8月15日在7:32

-1表示“通常,我将接口视为Semi(至多)OOP功能,它提供了继承,但没有提供真正的多态性。”我非常不同意。恰恰相反,接口全都与多态有关,而不主要与继承有关。他们将多个分类归于一个类型。如果可以,请避免使用IEMI;如果不能,则应按照@supercat的建议进行委派。不要避开接口。

– Aluan Haddad
16-2-4在3:17

“ EIMI不能通过派生类型来调用。” <<什么?这不是真的。如果我在类型上显式实现一个接口,则可以从该类型派生,仍然可以将其强制转换为该接口以调用该方法,就像实现该类型所必须的一样。所以不确定您在说什么。即使在派生类型中,我也可以将“ this”简单地转换为所讨论的接口,以达到显式实现的方法。

– Triynko
18-10-31在21:09



#5 楼

原因#1
当我想阻止“编程到实现”(设计模式中的设计原理)时,我倾向于使用显式接口实现。
例如,在基于MVP的Web应用程序中: />
public interface INavigator {
    void Redirect(string url);
}

public sealed class StandardNavigator : INavigator {
    void INavigator.Redirect(string url) {
        Response.Redirect(url);
    }
}

现在,另一个类(例如presenter)不太可能依赖于StandardNavigator实现,而更可能依赖于INavigator接口(因为需要将实现强制转换为接口才能使用Redirect方法)。
原因#2
我可能使用显式接口实现的另一个原因是保持类的“默认”接口更干净。例如,如果我正在开发ASP.NET服务器控件,则可能需要两个接口:Web开发人员使用的类的主接口;和
我开发的演示者使用的“隐藏”接口来处理控件的逻辑

下面是一个简单的示例。这是一个列出客户的组合框控件。在此示例中,网页开发人员对填充列表不感兴趣。相反,他们只是希望能够通过GUID选择客户或获取所选客户的GUID。演示者将在第一页加载时填充该框,并且此演示者由控件封装。 。
但这不是银弹炮弹
我不建议总是使用显式的接口实现。这些只是两个示例,它们可能会有所帮助。

#6 楼

除了已经说明的其他原因之外,在这种情况下,一个类正在实现两个不同的接口,这些接口具有相同的名称和签名的属性/方法。

/>这段代码可以编译并运行正常,但是Title属性是共享的。这是我们可以使用显式接口的时候。 (或以其他方式)显式。也许可以将其用作Title的默认实现-这样就不必修改现有代码即可将Class1转换为IBook或IPerson。标题,Class1的使用者必须首先将Class1的实例显式转换为IBook或IPerson-否则代码将无法编译。

#7 楼

我大多数时候都使用显式接口实现。这是主要原因。
重构更安全

更改接口时,最好由编译器检查它。使用隐式实现会更难。碰巧有一个与新签名具有相同签名的方法。这可能会导致意外的行为,并且使我痛苦多次。调试时很难“看到”,因为该功能可能无法与文件中的其他接口方法一起定位(下面提到的自记录问题)。隐式实现的方法将突然变成死代码,但是显式实现的方法将被编译错误捕获。即使死代码可以很好地保留,我也要被迫对其进行检查和推广。隐式实现,因此编译器可以执行额外的检查。由于需要使用'override'和'new',因此虚拟方法不存在上述任何一个问题。没问题。但是,对于我自己的界面,我无法预测它们何时/如何改变。一个类,将需要更多的工作来确定它是接口的一部分。有人可能不得不对此发表评论,或者将其放在一组其他接口实现中,全部都放在一个区域或分组评论中,即“ ITask的实现”。当然,只有在组标题不在屏幕外时,该方法才起作用。界面实现

我认为接口比公共方法更“公共”,因为它们被设计为仅暴露混凝土类型的一些表面积。它们将类型简化为功能,行为,特征集等。在实现中,我认为保持这种分离是很有用的。我遇到了显式的接口实现,我的大脑转向“代码协定”模式。通常,这些实现只是简单地转交给其他方法,但是有时它们会进行额外的状态/参数检查,转换输入参数以更好地匹配内部需求,甚至进行翻译以实现版本控制(即,多代接口都简化为通用实现)。

(我意识到,公众也是代码契约,但是接口要强大得多,尤其是在接口驱动的代码库中,直接使用具体类型通常是内部代码的标志。) />
相关:乔恩(Jon)的原因2。 >
需要时,根据歧义消除或需要内部接口

不鼓励“编程到实现”(Jon理由1)

问题

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


值类型,因为这将需要装箱并降低性能。这不是严格的规则,它取决于接口及其使用方式。可比吗?隐式的IFormattable?可能是显式的。
具有经常被直接调用的方法的临时系统接口(例如IDisposable.Dispose)。具体类型并想调用显式接口方法。我用以下两种方法之一来处理:


添加公共对象,并将接口方法转发给他们以实施。在内部进行操作时,通常发生在使用更简单的接口的情况下。如果有多个接口需要此功能,请将该名称扩展到我之外(根据我的经验,我很少有此需求)。

#8 楼

如果您明确实现,则只能通过接口类型的引用来引用接口成员。作为实现类类型的引用不会公开那些接口成员。

如果您的实现类不是公共的,则除了用于创建该类的方法(可以是工厂或IoC)容器),以及接口方法(当然),那么,我看不到显式实现接口的任何好处。未使用,允许您稍后更改该实现。我想,“确定”是“优势”。构造良好的实现无需显式实现即可完成此任务。 -public成员。

和很多事情一样,优点是缺点(反之亦然)。显式实现接口将确保您的具体类实现代码不公开。

评论


好的答案,比尔。其他答案很好,但是您(在您的观点之上)提供了一些其他客观观点,这使我更容易掌握。像大多数事物一样,隐式或显式实现也各有利弊,因此您只需要针对特定​​的场景或用例使用最佳方案即可。我想说那些试图更好地弄清楚这一点的人将从阅读您的答案中受益。

–丹尼尔·伊格尔(Daniel Eagle)
2014年12月2日在18:21

#9 楼

隐式接口实现是在其中具有与接口具有相同签名的方法。

显式接口实现是在其中显式声明该方法所属的接口。

interface I1
{
    void implicitExample();
}

interface I2
{
    void explicitExample();
}


class C : I1, I2
{
    void implicitExample()
    {
        Console.WriteLine("I1.implicitExample()");
    }


    void I2.explicitExample()
    {
        Console.WriteLine("I2.explicitExample()");
    }
}


MSDN:隐式和显式接口实现

#10 楼

每个实现接口的类成员都将导出一个声明,该声明在语义上与VB.NET接口声明的编写方式类似,例如,

Public Overridable Function Foo() As Integer Implements IFoo.Foo


通常会与接口成员的成员匹配,而类成员通常是公共的,而这一切都不是必需的。还可以声明:

Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo


在这种情况下,将允许类及其派生类使用名称IFoo_Foo访问类成员,但外部世界只能是可以通过强制转换为IFoo来访问该特定成员。在接口方法在所有实现中都具有指定行为,但仅在某些实现中有用的行为的情况下,这种方法通常是好的只读集合的​​IList<T>.Add方法的指定行为是抛出NotSupportedException]。不幸的是,在C#中实现接口的唯一正确方法是:

int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }


不太好。

#11 楼

前面的答案解释了为什么最好使用C#显式实现接口(出于大多数正式原因)。但是,在一种情况下,必须强制执行显式实现:为了避免在接口为非public的情况下泄漏封装,而实现类为public

>上述泄漏是不可避免的,因为根据C#规范,“所有接口成员都隐式具有公共访问权限”。结果,即使接口本身是例如,隐式实现也必须给予public访问。 internal

C#中的隐式接口实现非常方便。实际上,许多程序员一直/在任何地方都使用它,而无需进一步考虑。最好的情况下会导致表面混乱,最坏的情况下会导致封装泄漏。其他语言,例如F#,甚至不允许使用。

#12 楼

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

问题和解决方案在C#内部接口一文中得到了很好的解释。

例如,如果要保护应用程序层之间的对象泄漏,可以使用此技术指定可能导致泄漏的成员的不同可见性。