本文共 8681 字,大约阅读时间需要 28 分钟。
在C#中隐式和显式实现接口有何区别?
什么时候应该使用隐式,什么时候应该使用显式?
彼此之间是否有优点和/或缺点?
Microsoft的官方指南(来自第一版 )指出, 不建议使用显式实现 ,因为它会给代码带来意想不到的行为。
我认为,在您未将事物作为接口传递时 ,该指南在IoC之前非常有效 。
任何人都可以谈谈这方面吗?
显式接口实现的一个重要用途是在需要实现具有混合可见性的接口时。
文很好地解释了该问题和解决方案。
例如,如果要保护应用程序层之间的对象泄漏,则可以使用此技术指定可能导致泄漏的成员的不同可见性。
隐式是当您通过类中的成员定义接口时。 显式的是您在接口上的类中定义方法时。 我知道这听起来令人困惑,但这就是我的意思: IList.CopyTo
将隐式实现为:
public void CopyTo(Array array, int index){ throw new NotImplementedException();}
并明确表示为:
void ICollection.CopyTo(Array array, int index){ throw new NotImplementedException();}
不同之处在于,隐式实现允许您通过将接口强制转换为该类以及接口本身来创建的类来访问该接口。 显式实现仅允许您通过将接口强制转换为接口本身来访问该接口。
MyClass myClass = new MyClass(); // Declared as concrete classmyclass.CopyTo //invalid with explicit((IList)myClass).CopyTo //valid with explicit.
我主要使用显式的来保持实现的整洁,或者在我需要两个实现时。 无论如何,我很少使用它。
我确信还有更多理由使用/不使用其他人会张贴的明确内容。
有关每个主题背后的出色推理,请参阅此线程中的 。
隐式定义将只是将接口所需的方法/属性等作为公共方法直接添加到类中。
显式定义仅在直接使用接口而不是基础实现时才强制成员公开。 在大多数情况下,这是首选。
如果您明确实现,则只能通过接口类型的引用来引用接口成员。 作为实现类类型的引用不会公开那些接口成员。
如果您的实现类不是公共的,除了用于创建该类的方法(可以是工厂或容器)之外,而且除了接口方法(当然)外,那么我看不出显式实现的任何优势接口。
否则,显式实现接口可确保不使用对具体实现类的引用,从而允许您稍后更改该实现。 我想,“确定”是“优势”。 构造合理的实现无需显式实现即可完成此操作。
在我看来,缺点是您会发现自己在实现代码中可以访问非公共成员的接口强制类型转换。
与许多事物一样,优势是劣势(反之亦然)。 显式实现接口将确保不暴露您的具体类实现代码。
除了已经提供的出色答案外,在某些情况下,还要求显式实现以使编译器能够确定所需的内容。 以IEnumerable<T>
为例,它可能经常出现。
这是一个例子:
public abstract class StringList : IEnumerable{ private string[] _list = new string[] {"foo", "bar", "baz"}; // ... #region IEnumerable Members public IEnumerator GetEnumerator() { foreach (string s in _list) { yield return s; } } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion}
此处, IEnumerable<string>
实现IEnumerable
,因此我们也需要这样做。 但是请放心,通用版本和普通版本都以相同的方法签名实现函数 (C#为此忽略返回类型)。 这是完全合法的。 编译器如何解析要使用的? 它迫使您最多只有一个隐式定义,然后它可以解析所需的任何内容。
即。
StringList sl = new StringList();// uses the implicit definition.IEnumeratorenumerableString = sl.GetEnumerator();// same as above, only a little more explicit.IEnumerator enumerableString2 = ((IEnumerable )sl).GetEnumerator();// returns the same as above, but via the explicit definitionIEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator();
PS:IEnumerable的显式定义中的一小部分间接起作用的原因是,在函数内部,编译器知道变量的实际类型是StringList,这就是它解析函数调用的方式。 用于实现某些核心接口的某些抽象层的绝妙事实似乎已经积累。
除了已经说明的其他原因之外,在这种情况下,类正在实现两个不同的接口,这些接口的属性/方法具有相同的名称和签名。
////// This is a Book/// interface IBook{ string Title { get; } string ISBN { get; }}////// This is a Person/// interface IPerson{ string Title { get; } string Forename { get; } string Surname { get; }}////// This is some freaky book-person./// class Class1 : IBook, IPerson{ ////// This method is shared by both Book and Person /// public string Title { get { string personTitle = "Mr"; string bookTitle = "The Hitchhikers Guide to the Galaxy"; // What do we do here? return null; } } #region IPerson Members public string Forename { get { return "Lee"; } } public string Surname { get { return "Oades"; } } #endregion #region IBook Members public string ISBN { get { return "1-904048-46-3"; } } #endregion}
该代码将编译并运行正常,但是Title属性是共享的。
显然,我们希望返回的Title值取决于我们是否将Class1视为一本书还是一本书。 这是我们可以使用显式接口的时候。
string IBook.Title{ get { return "The Hitchhikers Guide to the Galaxy"; }}string IPerson.Title{ get { return "Mr"; }}public string Title{ get { return "Still shared"; }}
请注意,显式接口定义被推断为Public-因此,您不能显式声明它们为public(或其他方式)。
还请注意,您仍然可以拥有“共享”版本(如上所示),但是尽管这是可能的,但是否存在这样的属性值得怀疑。 也许可以将其用作Title的默认实现-这样就不必修改现有代码即可将Class1转换为IBook或IPerson。
如果您未定义“共享”(隐式)标题,则Class1的使用者必须首先将Class1的实例显式转换为IBook或IPerson-否则代码将无法编译。
每个实现接口的类成员都导出一个声明,该声明在语义上类似于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 ... }
不太好
我大多数时候都使用显式接口实现。 这是主要原因。
重构更安全
更改接口时,最好由编译器进行检查。 对于隐式实现,这更困难。
我想到两种常见的情况:
在接口中添加一个函数,在该接口中,实现该接口的现有类已经碰巧具有与新方法具有相同签名的方法 。 这可能会导致意外的行为,并且使我痛苦多次。 调试时很难“看到”,因为该功能可能未与文件中的其他接口方法一起定位(下面提到的自记录问题)。
从接口删除功能 。 隐式实现的方法将突然变成死代码,但显式实现的方法将被编译错误捕获。 即使死代码可以很好地保留,我也想被迫对其进行审查和推广。
不幸的是,C#没有关键字可以迫使我们将方法标记为隐式实现,因此编译器可以进行额外的检查。 由于需要使用“覆盖”和“新”,因此虚拟方法没有上述任何一个问题。
注意:对于固定或很少更改的接口(通常来自供应商API),这不是问题。 但是,对于我自己的界面,我无法预测它们何时/如何变化。
自我记录
如果我在类中看到“ public bool Execute()”,它将需要更多的工作来确定它是接口的一部分。 有人可能不得不这样说,或者将其放在一组其他接口实现中,全部放在一个区域或分组注释中,即“ ITask的实现”。 当然,这仅在组标题不在屏幕外时才有效。
而:'bool ITask.Execute()'清晰明确。
界面实现清晰分离
我认为接口比公共方法更“公共”,因为它们被设计为仅暴露混凝土类型的一些表面积。 它们将类型简化为功能,行为,特质等。在实现中,我认为保持这种分离是有用的。
当我浏览类的代码时,当我遇到显式的接口实现时,我的大脑转向“代码协定”模式。 通常,这些实现只是简单地转交给其他方法,但是有时它们会进行额外的状态/参数检查,转换输入参数以更好地匹配内部需求,甚至进行翻译以实现版本控制(即多代接口都可以简化为通用实现)。
(我意识到,公众也是代码契约,但是接口要强大得多,尤其是在接口驱动的代码库中,直接使用具体类型通常是仅内部代码的标志。)
相关: 。
等等
加上此处其他答案中已经提到的优点:
并不是所有的乐趣和幸福。 在某些情况下,我坚持使用隐式:
另外,当您实际上具有具体的类型并想要调用显式接口方法时,进行转换可能会很痛苦。 我用以下两种方式之一来处理:
public IMyInterface I { get { return this; } }
public IMyInterface I { get { return this; } }
这应该得到内联)和呼叫foo.I.InterfaceMethod()
如果有多个接口需要此功能,请将该名称扩展到我之外(根据我的经验,我很少有此需求)。 当我想阻止“编程到实现”( ”)时,我倾向于使用显式接口实现。
例如,在基于的Web应用程序中:
public interface INavigator { void Redirect(string url);}public sealed class StandardNavigator : INavigator { void INavigator.Redirect(string url) { Response.Redirect(url); }}
现在,另一个类(例如 )不太可能依赖于StandardNavigator实现,而更可能依赖于INavigator接口(因为需要将实现强制转换为使用Redirect方法的接口)。
我可能会使用显式接口实现的另一个原因是保持类的“默认”接口更整洁。 例如,如果我正在开发服务器控件,则可能需要两个接口:
下面是一个简单的示例。 这是一个列出客户的组合框控件。 在此示例中,网页开发人员对填充列表不感兴趣。 相反,他们只是希望能够通过GUID选择客户或获取所选客户的GUID。 演示者将在第一页加载时填充该框,并且该演示者由控件封装。
public sealed class CustomerComboBox : ComboBox, ICustomerComboBox { private readonly CustomerComboBoxPresenter presenter; public CustomerComboBox() { presenter = new CustomerComboBoxPresenter(this); } protected override void OnLoad() { if (!Page.IsPostBack) presenter.HandleFirstLoad(); } // Primary interface used by web page developers public Guid ClientId { get { return new Guid(SelectedItem.Value); } set { SelectedItem.Value = value.ToString(); } } // "Hidden" interface used by presenter IEnumerableICustomerComboBox.DataSource { set; }}
演示者填充数据源,并且网页开发人员无需知道其存在。
我不建议始终使用显式接口实现。 这些只是两个示例,它们可能会有所帮助。
通过C#引用CLR的Jeffrey Richter
(EIMI装置E xplicit 我覆盖整个院落中号 ethod 我 mplementation)对于您而言,了解使用EIMI时存在的一些后果至关重要。 并且由于这些后果,您应尝试尽可能避免EIMI。 幸运的是,通用接口可以帮助您避免EIMI。 但是有时仍然需要使用它们(例如,实现两个具有相同名称和签名的接口方法)。 以下是EIMI的主要问题:
- 没有文档说明类型如何专门实现EIMI方法,也没有Microsoft Visual Studio 支持。
- 值类型实例在投射到接口时被装箱。
- EIMI不能通过派生类型调用。
如果使用接口引用,则可以在任何派生类上将任何虚拟链显式替换为EIMI,并且将此类对象强制转换为接口时,将忽略您的虚拟链并调用显式实现。 只是多态而已。
EIMI还可以用于从基本Framework Interfaces的实现(例如IEnumerable <T>)隐藏非强类型接口成员,因此您的类不会直接公开非强类型方法,但是在语法上是正确的。
前面的答案解释了为什么最好使用 C#显式实现接口(出于大多数正式原因)。 但是,在一种情况下, 必须强制执行显式实现:为了避免在接口为非public
但实现类为public
时泄漏封装。
// 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() { } }
上述泄漏是不可避免的,因为根据 ,“所有接口成员都隐式具有公共访问权限”。 结果,即使接口本身是internal
,隐式实现也必须提供public
访问权限。
C#中的隐式接口实现非常方便。 在实践中,许多程序员一直/在任何地方都使用它,而无需进一步考虑。 这最好导致混乱的表面,最坏导致封装泄漏。 其他语言(例如F#) 。
隐式接口实现是您拥有具有相同接口签名的方法的地方。
显式接口实现是在其中显式声明该方法所属的接口的地方。
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:
转载地址:http://krogj.baihongyu.com/