c#接口。隐式实现与显式实现

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

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

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


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

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

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

164743 次浏览

隐式的是指通过类中的成员定义接口。显式的是在接口上的类中定义方法的时候。我知道这听起来令人困惑,但这就是我的意思: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,其他人会发布。

请参阅本线程中的< >强在下一篇文章< / >强,以了解它们背后的优秀推理。

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

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

  1. 通过直接使用接口,您没有承认, 并将您的代码耦合到底层实现
  2. 如果你已经有一个公共属性Name In 你的代码和你想要实现的接口也有 Name属性,显式执行将使两者分开。甚至 如果他们做同样的事情,我仍然委托显式 调用Name属性。你永远不会知道,你可能想要改变 Name如何为普通类工作,Name如何为接口工作 属性稍后生效 如果你隐式地实现了一个接口,那么你的类现在公开了 的客户端相关的新行为 接口,这意味着你没有保持你的类简洁 (我的观点)。

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

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

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

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

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

除了已经提供的优秀答案之外,还有一些情况需要显式实现,以便编译器能够找出需要什么。以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核心接口似乎已经积累起来了。

除了前面提到的其他原因外,这是指一个类实现了两个不同的接口,而这两个接口的属性/方法具有相同的名称和签名。

/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
string Title { get; }
string ISBN { get; }
}


/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
string Title { get; }
string Forename { get; }
string Surname { get; }
}


/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
/// <summary>
/// This method is shared by both Book and Person
/// </summary>
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 -因此您不能显式地将它们声明为Public(或其他)。

还要注意,您仍然可以拥有一个“共享”版本(如上所示),但虽然这是可能的,但这样一个属性的存在是值得怀疑的。也许它可以用作Title的默认实现——这样就不必修改现有的代码来强制转换Class1到IBook或IPerson。

如果你没有定义“共享的”(隐式的)Title, Class1 必须的消费者会先显式地将Class1的实例转换为IBook或IPerson -否则代码将无法编译。

原因# 1

当我不鼓励“为实现编程”时,我倾向于使用显式接口实现。(# EYZ0)。

例如,在一个基于# eyz0的web应用程序中:

public interface INavigator {
void Redirect(string url);
}


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

现在,另一个类(例如主持人)不太可能依赖于StandardNavigator实现,而更可能依赖于navigator接口(因为实现需要转换为接口来使用Redirect方法)。

原因# 2

我可能会使用显式接口实现的另一个原因是保持类的“默认值”。界面干净。例如,如果我正在开发一个ASP。网服务器控件,我可能需要两个接口:

  1. 类的主接口,供网页开发人员使用;而且
  2. 一个“hidden"我开发的用于处理控件逻辑的演示者使用的接口

下面是一个简单的例子。这是一个列出客户的组合框控件。在这个例子中,网页开发人员对填充列表不感兴趣;相反,他们只是希望能够通过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
IEnumerable<CustomerDto> ICustomerComboBox.DataSource { set; }
}

演示者填充数据源,web页面开发人员永远不需要知道它的存在。

但这不是银炮弹

我不建议总是使用显式接口实现。这只是它们可能有用的两个例子。

引用Jeffrey Richter from CLR via c#
(EIMI意味着Explicit nterface # eyz3ethd # eyz2implementation)

这对你来说是至关重要的 理解一些分支 使用eim时不存在。因为 这些分支,你应该尝试 尽量避免EIMIs。 幸运的是,通用接口有帮助 你会避免使用eim。但 也许还有你需要的时候 使用它们(例如实现两个 具有相同名称的接口方法 和签名)。这里是大的 EIMIs的问题:

  • 没有文档解释类型是如何具体地 实现一个EIMI方法,在那里 没有微软的Visual Studio 李# EYZ0支持。< / >
  • 值类型实例在转换为接口时被装箱。
  • 派生类型不能调用EIMI。

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

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

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

显式接口实现是显式声明方法属于哪个接口的地方。

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: # EYZ0

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

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

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

实现接口的每个类成员都导出一个语义上类似于VB的声明。NET接口声明被编写,例如:

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

尽管类成员的名称经常与接口成员的名称相匹配,而且类成员通常是public的,但这些都不是必需的。你也可以声明:

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

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

这是自我记录

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

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

清晰分离的接口实现

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

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

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

相关:# EYZ0。

等等

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

问题

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

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

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

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

前面的回答解释了为什么在c#中显式实现接口可能是preferrable(主要是出于形式上的原因)。然而,有一种情况下显式实现是强制性的:为了避免在接口不是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() { } }

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

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

我发现自己最近更经常使用显式的实现,原因如下:

  • 总是从一开始就使用显式可以防止任何命名冲突,在这种冲突中,无论如何都需要显式实现

  • 消费者“被迫”;使用接口(当你使用DI时,他们应该/必须这样做)

  • < p >没有“zombie"实现中的成员——如果不从实现中删除,从接口声明中删除任何成员都会导致编译器错误

  • 可选参数的默认值,以及对通用参数的约束被自动采用-不需要写它们两次并保持它们同步