声明
本文内容来自微软 MVP solenovex 的视频教程——真会C#?- 第3章 创建类型,大致和第 9 课—— 接口简介 对应。可在 GitHub 中查看 C# 视频教程的配套PPT
本文主要包括以下内容:
- 接口的扩展
- 显式的接口实现
- virtual 的实现接口成员
- 在子类中重新实现接口
- 接口与装箱
接口
接口与 class 类似,但是它只为其成员提供了规格,而没有提供具体的实现,接口的成员都是隐式抽象的。一个 class 或者 struct 可以实现多个接口。
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
接口的成员都是隐式 public 的,不可以声明访问修饰符,实现接口对它的所有成员进行 public 的实现:
internal class Countdown : IEnumerator
{
int count = 11;
public bool MoveNext() => count-- > 0;
public object Current => count;
public void Reset() { throw new NotSupportedException(); }
}
可以隐式的把一个对象转化成它实现的接口:
IEnumerator e = new Countdown();
while (e.MoveNext())
Console.Write (e.Current); // 109876543210
虽然 Countdown 是一个 internal 的 class,但是可以通过把它的实例转化成 IEnumerator 接口来公共的访问它的成员。
接口的扩展
接口可以继承其它接口:
public interface IUndoable { void Undo(); }
public interface IRedoable : IUndoable { void Redo(); }
IRedoable 继承了 IUndoable 的所有成员。
显式的接口实现
实现多个接口的时候可能会造成成员签名的冲突。通过显式实现接口成员可以解决这个问题。
interface I1 { void Foo(); }
interface I2 { int Foo(); }
public class Widget : I1, I2
{
public void Foo()
{
Console.WriteLine ("Widget's implementation of I1.Foo");
}
int I2.Foo()
{
Console.WriteLine ("Widget's implementation of I2.Foo");
return 42;
}
}
本例中,想要调用相应实现的接口方法,只能把其实例转化成相应的接口才行:
Widget w = new Widget();
w.Foo(); // Widget's implementation of I1.Foo
((I1)w).Foo(); // Widget's implementation of I1.Foo
((I2)w).Foo(); // Widget's implementation of I2.Foo
另一个显式实现接口成员的理由是故意隐藏那些对于类型来说不常用的成员。
virtual 的实现接口成员
隐式实现的接口成员默认是 sealed 的。如果想要进行重写的话,必须在基类中把成员标记为 virtual 或者 abstract。
public interface IUndoable { void Undo(); }
public class TextBox : IUndoable
{
public virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}
public class RichTextBox : TextBox
{
public override void Undo() => Console.WriteLine ("RichTextBox.Undo");
}
无论是转化为基类还是转化为接口来调用接口的成员,调用的都是子类的实现:
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo
((IUndoable)r).Undo(); // RichTextBox.Undo
((TextBox)r).Undo(); // RichTextBox.Undo
显式实现的接口成员不可以被标记为 virtual,也不可以通过寻常的方式来重写,但是可以对其进行重新实现。
在子类中重新实现接口
子类可以重新实现父类已经实现的接口成员。重新实现会“劫持”成员的实现(通过转化为接口然后调用),无论在基类中该成员是否是 virtual 的。无论该成员是显式的还是隐式的实现(但最好还是显式实现的)。
public interface IUndoable { void Undo(); }
public class TextBox : IUndoable
{
void IUndoable.Undo() => Console.WriteLine ("TextBox.Undo");
}
public class RichTextBox : TextBox, IUndoable
{
public void Undo() => Console.WriteLine ("RichTextBox.Undo");
}
转化为接口后调用重新实现的成员,就是调用子类的实现:
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo Case 1
((IUndoable)r).Undo(); // RichTextBox.Undo Case 2
如果 Textbox 是隐式实现的 Undo:
public class TextBox : IUndoable
{
public void Undo() => Console.WriteLine ("TextBox.Undo");
}
那么:
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo Case 1
((IUndoable)r).Undo(); // RichTextBox.Undo Case 2
((TextBox)r).Undo(); // TextBox.Undo Case 3
说明重新实现接口这种劫持只对转化为接口后的调用起作用,对转化为基类后的调用不起作用。重新实现适用于重写显式实现的接口成员。
重新实现接口的替代方案
即使是显式实现的接口,接口的重新实现也可能有一些问题:
- 子类无法调用基类的方法
- 基类的开发人员没有预见到方法会被重新实现,并且可能不允许潜在的后果
最好的办法是设计一个无需重新实现的基类:
- 隐式实现成员的时候,按需标记 virtual
- 显式实现成员的时候,可以这样做:
public class TextBox : IUndoable
{
void IUndoable.Undo() => Undo(); // Calls method below
protected virtual void Undo() => Console.WriteLine ("TextBox.Undo");
}
public class RichTextBox : TextBox
{
protected override void Undo() => Console.WriteLine("RichTextBox.Undo");
}
如果不想有子类,那么直接把 class 给 sealed。
接口与装箱
把 struc t转化为接口会导致装箱,调用 struct 上隐式实现的成员不会导致装箱。
interface I { void Foo(); }
struct S : I { public void Foo() {} }
...
S s = new S();
s.Foo(); // No boxing.
I i = s; // Box occurs when casting to interface.
i.Foo();
网友评论