文档资料

接口

什么是接口

  • 接口和 class 类似,但是它只为其成员提供了规格,而没有提供具体的实现。
  • 接口成员都是隐式抽象的。
  • 一个类或者结构体可以实现多个接口。
public interface IEnumerator
{
	bool MoveNext();
	object Current { get; }
	void Reset();
}

接口的实现

  • 接口的成员都是隐式公共的,不可以声明访问修饰符。
  • 实现接口对它的所有成员进行 public 的实现:
internal class Countdown : IEnumerator
{
	int count = 11;
	public bool MoveNext() => count-- > 0;
	public object Current => count;
	public void Reset() { throw new NotSupportedException();}
}

默认接口方法

接口的扩展

  • 接口可以继承其他接口
public interface IUndoable {void Undo();}
public interface IRedoable : IUndoable {void Redo();}
  • IRedoable 继承了 IUndoable 的所有成员。

显式接口的实现

  • 实现多个接口的时候可能会造成成员签名的冲突。通过显式实现接口成员可以解决这个问题。
  • 另一个显式实现接口成员的理由是故意隐藏那些对于类型来说不常用的成员。

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");
}
  • 显式实现的接口成员不可以被标记为 virtual,也不可以有通过寻常的方式来重写,但是可以对其进行重新实现。

在子类中重新实现接口

  • 子类可以重新实现父类已经实现的接口成员。
  • 重新实现会“劫持”成员的实现(通过转化为接口然后调用),无论在基类中该成员是否是 virtual 的。无论该成员是显式还是隐式的实现(但最好还是显式实现的)。
public interface IUndoable { void Undo();}

public class TextBox : IUndoable
{
	public void Undo() => Console.WriteLine("TextBox.Undo");
}

public class RichTextBox : TextBox, IUndoable
{
	public void Undo() => Console.WriteLine("RichTextBox.Undo");
}
public interface IFoo
{
	void Do();
}

public class Parent : IFoo
{
	void Do() => Console.WriteLine("Parent");
}

public class Child : Parent
{
	public void Do() => Console.WriteLine("Child");
}

class Program
{
	static void Main(string[] args)
	{
		var c = new Child();
		c.Do();			// Child
		((IFoo) c).Do();	// Parent
	}
}
public class Child : Parent, IFoo
{
	public void Do() => Console.WriteLine("Child");
}

class Program
{
	static void Main(string[] args)
	{
		var c = new Child();
		c.Do();			// Child
		((IFoo) c).Do();	// Child
		((Parent) c).Do();	// Parent
	}
}
  • 如果 TextBox 是隐式实现的 Undo
public class TextBox : IUndable
{
	public void Undo() => Console.WriteLine("TextBox.Undo");
}
  • 那么:
var RichTextBox r = new RichTextBox();
r.Undo();			// RichTextBox.Undo
((IRundoable) r).Undo();	// RichTextBox.Undo
((TextBox) r).Undo();		// TextBox.Undo
  • 说明重新实现接口这种劫持只对转化为接口后的调用起作用,对转化为基类后的调用不起作用。
  • 重新实现适用于重写显示实现的接口成员。

重新实现接口的替代方案(重点)

  • 即使是显示实现的接口,接口的重新实现也可能有一些问题:
    • 子类无法调用基类的方法。
    • 基类的开发人员没有预料到方法会被重新实现,并且可能不允许潜在的后果。
  • 最好的办法是设计一个无需重新实现的基类。
    • 隐式实现成员的时候,按需标记 virtual
    • 显式实现成员的时候,可以这么做:
public class TextBox : IUndoable
{
	void IUndoable.Undo() => Undo();
	protected virtual void Undo() => COnsole.WriteLine("TextBox.Undo");
}

public class RichTextBox : TextBox
{
	protected override void Undo() => Console.WriteLine("RichTextBox.Undo");
}
  • 如果不想有子类,那么之境界把 classsealed

接口与装箱

  • 把结构体转化为接口会导致装箱。
  • 调用结构体上隐式实现的成员不会导致装箱。
interface I { void Foo(); }
struct S : I { public void Foo() {} }
...
var s = new S();
s.Foo();	// No boxing.
I i = s;	// Box occurs when casting to interface.
i.Foo();