在c#中隐式和显式实现接口有什么不同?
什么时候用隐式,什么时候用显式?
这两者之间有什么利弊吗?
微软的官方指南(来自第一版框架设计指南)指出,不建议使用显式实现,因为它会给代码带来意想不到的行为。
我认为这个准则在前ioc时代是非常有效的,当你不把东西作为接口传递的时候。
有人能谈谈这方面的问题吗?
在c#中隐式和显式实现接口有什么不同?
什么时候用隐式,什么时候用显式?
这两者之间有什么利弊吗?
微软的官方指南(来自第一版框架设计指南)指出,不建议使用显式实现,因为它会给代码带来意想不到的行为。
我认为这个准则在前ioc时代是非常有效的,当你不把东西作为接口传递的时候。
有人能谈谈这方面的问题吗?
当前回答
实现接口的每个类成员都导出一个语义上类似于VB的声明。NET接口声明被编写,例如:
Public Overridable Function Foo() As Integer Implements IFoo.Foo
尽管类成员的名称经常与接口成员的名称相匹配,而且类成员通常是public的,但这些都不是必需的。你也可以声明:
Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo
In which case the class and its derivatives would be allowed to access a class member using the name IFoo_Foo, but the outside world would only be able to access that particular member by casting to IFoo. Such an approach is often good in cases where an interface method will have specified behavior on all implementations, but useful behavior on only some [e.g. the specified behavior for a read-only collection's IList<T>.Add method is to throw NotSupportedException]. Unfortunately, the only proper way to implement the interface in C# is:
int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }
没那么好。
其他回答
我发现自己最近更经常地使用显式实现,原因如下:
Always using explicit from the starts prevents having any naming collisions, in which explicit implementation would be required anyways Consumers are "forced" to use the interface instead of the implementation (aka not "programming to an implementation") which they should / must do anyways when you're using DI No "zombie" members in the implementations - removing any member from the interface declaration will result in compiler errors if not removed from the implementation too Default values for optional parameters, as well constraints on generic arguments are automatically adopted - no need to write them twice and keep them in sync
原因# 1
我倾向于使用显式接口实现,当我不鼓励“编程到实现”(来自设计模式的设计原则)。
例如,在一个基于mvp的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。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
IEnumerable<CustomerDto> ICustomerComboBox.DataSource { set; }
}
演示者填充数据源,web页面开发人员永远不需要知道它的存在。
但这不是银炮弹
我不建议总是使用显式接口实现。这只是它们可能有用的两个例子。
隐式接口实现是指具有与接口相同签名的方法。
显式接口实现是显式声明方法属于哪个接口的地方。
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:隐式和显式接口实现
除了已经提供的优秀答案之外,还有一些情况需要显式实现,以便编译器能够找出需要什么。以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核心接口似乎已经积累起来了。
前面的回答解释了为什么用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#,甚至不允许这样做。