在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 class myclass.CopyTo //invalid with explicit ((IList)myClass).CopyTo //valid with explicit.
我主要使用explicit来保持实现干净,或者当我需要两个实现时.但不管我很少使用它.
我相信有更多的理由使用它/不使用它其他人会发布.
请参阅本主题中的下一篇文章,了解每个帖子背后的理由.
隐式定义只是将接口所需的方法/属性等直接添加到类作为公共方法.
显式定义仅在您直接使用接口而不是底层实现时强制公开成员.在大多数情况下这是优选的.
通过直接使用接口,您无法确认,并将代码耦合到底层实现.
如果你的代码中已经有一个公共属性Name,并且你想要实现一个也具有Name属性的接口,那么明确地执行它会使两者分开.即使他们做了同样的事情,我仍然会将显式调用委托给Name属性.您永远不会知道,您可能想要更改Name对普通类的工作方式以及Name,接口属性如何在以后工作.
如果你隐式地实现了一个接口,那么你的类现在暴露出可能只与接口客户端相关的新行为,这意味着你没有足够简洁地保持你的类(我的观点).
除了已经提供的优秀答案之外,还有一些情况需要显式实现,以便编译器能够找出所需的内容.看看IEnumerable
作为一个可能会经常出现的主要例子.
这是一个例子:
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
工具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 definition IEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator();
PS:IEnumerable的显式定义中的一小部分间接作用,因为在函数内部,编译器知道变量的实际类型是StringList,这就是它如何解析函数调用.实现某些抽象层的一些微不足道的事实似乎已经积累了一些.NET核心接口.
当我想阻止"编程实现"(设计模式的设计原则)时,我倾向于使用显式接口实现.
例如,在基于MVP的Web应用程序中:
public interface INavigator { void Redirect(string url); } public sealed class StandardNavigator : INavigator { void INavigator.Redirect(string url) { Response.Redirect(url); } }
现在,另一个类(例如演示者)不太可能依赖于StandardNavigator实现,并且更可能依赖于INavigator接口(因为需要将实现强制转换为接口以使用Redirect方法).
我可能采用显式接口实现的另一个原因是保持类的"默认"接口更清晰.例如,如果我正在开发ASP.NET服务器控件,我可能需要两个接口:
该类的主要接口,由网页开发人员使用; 和
我开发的用于处理控件逻辑的演示者使用的"隐藏"界面
一个简单的例子如下.这是一个列出客户的组合框控件.在此示例中,网页开发人员对填充列表不感兴趣; 相反,他们只是希望能够通过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引用杰弗里里希特
(EIMI意味着Ë xplicit 我覆盖整个院落中号 ethod 我 mplementation)
了解使用EIMI时存在的某些后果至关重要.由于这些后果,你应该尽量避免使用EIMI.幸运的是,通用接口可以帮助您避免使用EIMI.但有时您可能需要使用它们(例如实现两个具有相同名称和签名的接口方法).以下是EIMI的主要问题:
没有文档说明类型如何专门实现EIMI方法,并且没有Microsoft Visual Studio IntelliSense支持.
在转换为接口时,值类型实例会被装箱.
EIMI不能由派生类型调用.
如果使用接口引用ANY虚拟链可以在任何派生类上显式替换为EIMI,并且当将此类型的对象强制转换为接口时,将忽略您的虚拟链并调用显式实现.这不过是多态的.
EIMI还可以用于隐藏非强类型接口成员从基本框架接口的实现,如IEnumerable
除了已经说明的其他原因之外,这是一个类正在实现两个具有相同名称和签名的属性/方法的不同接口的情况.
////// 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视为Book还是Person.这是我们可以使用显式接口的时候.
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 - 因此您无法将它们声明为公开(或其他)显式.
另请注意,您仍然可以拥有"共享"版本(如上所示),但尽管这是可能的,但这种属性的存在是值得怀疑的.也许它可以用作Title的默认实现 - 这样就不必修改现有代码就可以将Class1强制转换为IBook或IPerson.
如果未定义"共享"(隐式)标题,则Class1的使用者必须首先将Class1的实例显式转换为IBook或IPerson,否则代码将无法编译.
我大部分时间都使用显式接口实现.以下是主要原因.
重构更安全
更改接口时,编译器可以检查它是否更好.隐式实现更难.
我想到两个常见的情况:
向接口添加一个函数,其中实现此接口的现有类恰好具有与新接口具有相同签名的方法.这可能导致意外行为,并且几次困扰我.调试时很难"看到",因为该函数很可能与文件中的其他接口方法无关(下面提到的自我记录问题).
从界面中删除功能.隐式实现的方法将突然死代码,但显式实现的方法将被编译错误捕获.即使死代码保持良好,我也希望被强制审查并推广它.
不幸的是,C#没有强制我们将方法标记为隐式实现的关键字,因此编译器可以进行额外的检查.由于需要使用'override'和'new',虚方法没有上述任何一个问题.
注意:对于固定或很少更改的接口(通常来自供应商API),这不是问题.但是,对于我自己的界面,我无法预测它们何时/如何改变.
它是自我记录的
如果我在一个类中看到'public bool Execute()',它将需要额外的工作来弄清楚它是接口的一部分.有人可能不得不评论它这样说,或者把它放在一组其他接口实现中,所有这些都在一个区域或分组评论中说"ITask的实现".当然,这仅在组头不在屏幕外时才有效.
鉴于:'bool ITask.Execute()'清晰明确.
明确分离接口实现
我认为界面比公共方法更"公开",因为它们被设计为只暴露具体类型的一些表面区域.它们将类型简化为一种能力,一种行为,一组特征等.在实施中,我认为保持这种分离是有用的.
当我查看类的代码时,当我遇到显式接口实现时,我的大脑转变为"代码契约"模式.这些实现通常只是转发到其他方法,但有时它们会进行额外的状态/参数检查,传入参数的转换以更好地匹配内部需求,甚至转换用于版本控制目的(即多代接口都归结为常见的实现).
(我意识到公共也是代码契约,但接口更强大,特别是在接口驱动的代码库中,直接使用具体类型通常是内部代码的标志.)
相关:Jon上面的原因2.
等等
加上其他答案中已经提到的优点:
根据需要,根据消歧或需要内部接口
劝阻"编程实现"(Jon的理由1)
这不是一切乐趣和快乐.在某些情况下,我坚持使用implicits:
值类型,因为这将需要拳击和较低的perf.这不是一个严格的规则,取决于界面以及如何使用它.IComparable的?隐.IFormattable?可能是显而易见的.
具有经常直接调用的方法的简单系统接口(如IDisposable.Dispose).
另外,当你实际上具有具体类型并且想要调用显式接口方法时,执行转换可能会很痛苦.我用以下两种方式之一处理这个问题:
添加公共并将接口方法转发给它们以实现.通常在内部工作时使用更简单的接口.
(我的首选方法)添加一个public IMyInterface I { get { return this; } }
(应该内联)并调用foo.I.InterfaceMethod()
.如果需要此功能的多个接口,请将名称扩展到I之外(根据我的经验,我很少有这种需求).
如果明确实现,则只能通过接口类型的引用引用接口成员.作为实现类类型的引用不会公开这些接口成员.
如果你的实现类不是公共的,除了用于创建类的方法(可以是工厂或IoC容器),除了接口方法(当然),那么我没有看到显式实现的任何优势接口.
否则,显式实现接口可确保不使用对具体实现类的引用,从而允许您稍后更改该实现.我认为,"确定"是"优势".一个精心设计的实现可以在没有明确实现的情况下实现此目
在我看来,缺点是你会发现自己在实现代码中向接口或从接口转换类型,这些接口可以访问非公共成员.
像许多事情一样,优点是缺点(反之亦然).显式实现接口将确保不暴露您的具体类实现代码.
隐式接口实现是指具有相同签名的接口的方法.
显式接口实现是您显式声明方法所属的接口的位置.
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:隐式和显式接口实现
实现接口的每个类成员都会导出一个声明,该声明在语义上类似于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
方法的指定行为是抛出NotSupportedException
].不幸的是,在C#中实现接口的唯一正确方法是:
int IFoo.Foo() { return IFoo_Foo(); } protected virtual int IFoo_Foo() { ... real code goes here ... }
不太好.