接口概述
接口是指定一组函数成员而不实现它们的引用类型。所以只能类和结构来实现接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| interface IInfo { string GetName(); string GetAge(); }
class CA : IInfo { public string Name; public int Age; public string GetName() { return Name; } public string GetAge() { return Age.ToString(); } }
class CB : IInfo { public string First; public string Last; public double PersonsAge; public string GetName() { return First + " " + Last; } public string GetAge() { return PersonsAge.ToString(); } } class Program { static void PrintInfo(IInfo item) { Console.WriteLine("Name: {0}, Age {1}", item.GetName(), item.GetAge()); }
static void Main() { CA a = new CA() { Name = "John Doe", Age = 35 }; CB b = new CB() { First = "Jane", Last = "Doe", PersonsAge = 33 };
PrintInfo(a); PrintInfo(b); } }
|
使用IComparable接口的示例
Array类的Sort方法其实依赖于一个叫作IComparable的接口,它声明在BCL中,包含唯一的方法CompareTo。
接口主体内包含CompareTo方法的声明,指定了它接受一个object类型的参数。
1 2 3 4
| public interface IComparable { int CompareTo(object obj); }
|
尽管在接口声明中没有为CompareTo方法提供实现,但IComparable接口的.NET文档中描述了该方法应该做的事情,你可以在创建实现该接口的类或结构时参考。文档中写道,在调用CompareTo方法时,它应该返回以下几个值之一:
- 负数值 如果当前对象小于参数对象;
- 正数值 如果当前对象大于参数对象;
- 零 如果两个对象在比较时相等。
Sort使用的算法依赖于使用元素的CompareTo方法来决定两个元素的次序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class MyClass : IComparable { public int TheValue; public int CompareTo(object obj) { MyClass mc = (MyClass)obj; if (this.TheValue < mc.TheValue) return -1; if (this.TheValue > mc.TheValue) return 1; return 0; } }
class Program { static void PrintOut(string s, MyClass[] mc) { Console.Write(s); foreach (var m in mc) Console.Write($"{ m.TheValue } "); Console.WriteLine(""); }
static void Main() { var myInt = new [] { 20, 4, 16, 9, 2 };
MyClass[] mcArr = new MyClass[5]; for (int i = 0; i < 5; i++) { mcArr[i] = new MyClass(); mcArr[i].TheValue = myInt[i]; }
PrintOut("Initial Order: ", mcArr); Array.Sort(mcArr); PrintOut("Sorted Order: ", mcArr); } }
|
1 2
| Initial Order: 20 4 16 9 2 Sorted Order: 2 4 9 16 20
|
声明接口
- 接口声明不能包含以下成员:
- 接口声明只能包含如下类型的非静态成员函数的声明:
- 这些函数成员的声明不能包含任何实现代码,必须使用分号代替每一个成员声明的主体。
- 按照惯例,接口名称必须从大写的I开始(比如 ISaveable)。
- 与类和结构一样,接口声明也可以分隔成分部接口声明。
1 2 3 4 5
| interface IMyInterface1 { int DoStuff ( int nVar1, long lVar2 ); double DoOtherStuff( string s, long x ); }
|
接口的访问性和接口成员的访问性之间有一些重要区别。
- 接口声明可以有任何的访问修饰符:
public、protected、internal 或 private。
- 然而,接口成员是隐式
public 的,不允许有任何访问修饰符,包括 public。
实现接口
只有类和结构才能实现接口。如Sort示例所示,要实现接口,类或结构必须:
1 2 3 4 5 6
| class Derived : MyBaseClass, IIfc1, IEnumerable, IComparable { ... }
|
简单接口的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| interface IIfc1 { void PrintOut(string s); }
class MyClass : IIfc1 { public void PrintOut(string s) { Console.WriteLine($"Calling through: { s }"); } }
class Program { static void Main() { MyClass mc = new MyClass(); mc.PrintOut("object"); } }
|
接口是引用类型
接口不仅仅是类或结构要实现的成员列表。它是一个引用类型。
不能直接通过类对象的成员访问接口。可以通过把类对象引用强制转换为接口类型来获取指向接口的引用。有了接口的引用,就可以使用点语法来调用接口的成员。但是,使用这个接口,你不能调用不属于这个接口成员的类成员。
1 2 3 4 5 6 7 8 9
| 接口 转换为接口 ↓ ↓ IIfc1 ifc = (IIfc1) mc; ↑ ↑ 接口引用 类对象引用
ifc.PrintOut ("interface"); ↑ 使用点语法通过接口引用调用
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| interface IIfc1 { void PrintOut(string s); }
class MyClass: IIfc1 { public void PrintOut(string s) { Console.WriteLine($"Calling through: { s }"); } }
class Program { static void Main() { MyClass mc = new MyClass(); mc.PrintOut("object");
IIfc1 ifc = (IIfc1)mc; ifc.PrintOut("interface"); } }
|
1 2
| Calling through: object Calling through: interface
|
接口和as运算符
如果尝试将类对象引用强制转换为类未实现的接口的引用,强制转换操作会抛出一个异常。可以通过使用as运算符来避免这个问题。
- 如果类实现了接口,表达式返回指向接口的引用。
- 如果类没有实现接口,表达式返回 null 而不是抛出异常。
1 2 3 4 5 6 7 8
| 类对象引用 接口名称 ↓ ↓ ILiveBirth b = a as ILiveBirth; ↑ ↑ 接口引用 运算符
if (b != null) Console.WriteLine($"Baby is called: {b.BabyCalled()}");
|
实现多个接口
- 类或结构可以实现任意数量的接口。
- 所有实现的接口必须列在基类列表中并以逗号分隔(如果有基类名称,则在其之后)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| interface IDataRetrieve { int GetData(); } interface IDataStore { void SetData( int x ); }
class MyData: IDataRetrieve, IDataStore { int Mem1; public int GetData() { return Mem1; } public void SetData( int x ){ Mem1 = x; } }
class Program { static void Main() { MyData data = new MyData(); data.SetData( 5 ); Console.WriteLine($"Value = { data.GetData() }"); } }
|
实现具有重复成员的接口
如果一个类实现了多个接口,并且其中一些接口成员具有相同的签名和返回类型,那么类可以实现单个成员来满足所有包含重复成员的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| interface IIfc1 { void PrintOut(string s); }
interface IIfc2 { void PrintOut(string t); }
class MyClass : IIfc1, IIfc2 { public void PrintOut(string s) { Console.WriteLine($"Calling through: { s }"); } }
class Program { static void Main() { MyClass mc = new MyClass(); mc.PrintOut("object"); } }
|
多个接口的引用
如果类实现了多个接口,我们可以获取每一个接口的独立引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| interface IIfc1 { void PrintOut(string s); }
interface IIfc2 { void PrintOut(string s); }
class MyClass : IIfc1, IIfc2 { public void PrintOut(string s) { Console.WriteLine($"Calling through: { s }"); } }
class Program { static void Main() { MyClass mc = new MyClass();
IIfc1 ifc1 = (IIfc1) mc; IIfc2 ifc2 = (IIfc2) mc;
mc.PrintOut("object");
ifc1.PrintOut("interface 1"); ifc2.PrintOut("interface 2"); } }
|
派生成员作为实现
实现接口的类可以从它的基类继承实现的代码。
IIfc1是一个具有PrintOut方法成员的接口。
MyBaseClass包含了一个叫作PrintOut的方法,它和IIfc1的方法声明相匹配。
Derived类有一个空的声明主体,但它派生自MyBaseClass,并在基类列表中包含了IIfc1。
- 即使
Derived的声明主体是空的,基类中的代码还是能满足实现接口方法的需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| interface IIfc1 { void PrintOut(string s); }
class MyBaseClass { public void PrintOut(string s) { Console.WriteLine($"Calling through: { s }"); } }
class Derived : MyBaseClass, IIfc1 { }
class Program { static void Main() { Derived d = new Derived(); d.PrintOut("object."); } }
|
显示接口成员实现
单个类可以实现多个接口需要的所有成员。如果希望为每一个接口分离实现的话,可以创建显式接口成员实现。显式接口成员实现有如下特性。
- 与所有接口实现相似,位于实现了接口的类或结构中。
- 它使用限定接口名称来声明,由接口名称和成员名称以及它们中间的点分隔符号构成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| interface IIfc1 { void PrintOut(string s); } interface IIfc2 { void PrintOut(string t); }
class MyClass : IIfc1, IIfc2 { void IIfc1.PrintOut(string s) { Console.WriteLine($"IIfc1: { s }"); }
void IIfc2.PrintOut(string s) { Console.WriteLine($"IIfc2: { s }"); } }
class Program { static void Main() { MyClass mc = new MyClass();
IIfc1 ifc1 = (IIfc1) mc; ifc1.PrintOut("interface 1");
IIfc2 ifc2 = (IIfc2) mc; ifc2.PrintOut("interface 2"); } }
|
访问显示接口成员实现
显式接口成员实现只可以通过指向接口的引用来访问,其他的类成员都不可以直接访问它们。
这个限制对继承产生了重要的影响。由于其他类成员不能直接访问显式接口成员实现,派生类的成员也不能直接访问它们。它们必须总是通过接口的引用来访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MyClass : IIfc1 { void IIfc1.PrintOut(string s) { Console.WriteLine("IIfc1"); }
public void Method1() { PrintOut("..."); this.PrintOut("...");
((IIfc1)this).PrintOut("..."); } }
|
接口可以继承接口
接口实现可以从基类被继承,而接口本身也可以从一个或多个接口继承而来。
要指定某个接口继承其他的接口,应在接口声明中把基接口名称以逗号分隔的列表形式放在接口名称后面的冒号之后。
类在基类列表中只能有一个类名,而接口可以在基接口列表中有任意多个接口。
- 列表中的接口本身可以继承其他接口。
- 结果接口包含它声明的所有成员和基接口的所有成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| interface IDataRetrieve { int GetData(); }
interface IDataStore { void SetData( int x ); }
interface IDataIO: IDataRetrieve, IDataStore { }
class MyData: IDataIO { int nPrivateData; public int GetData( ) { return nPrivateData; } public void SetData( int x ) { nPrivateData = x; } }
class Program { static void Main( ) { MyData data = new MyData (); data.SetData( 5 ); Console.WriteLine("{0}", data.GetData()); } }
|
不同类实现一个接口的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| interface ILiveBirth { string BabyCalled(); }
class Animal { }
class Cat : Animal, ILiveBirth { string ILiveBirth.BabyCalled() { return "kitten"; } }
class Dog : Animal, ILiveBirth { string ILiveBirth.BabyCalled() { return "puppy"; } }
class Bird : Animal { }
class Program { static void Main() { Animal[] animalArray = new Animal[3]; animalArray[0] = new Cat(); animalArray[1] = new Bird(); animalArray[2] = new Dog(); foreach( Animal a in animalArray ) { ILiveBirth b = a as ILiveBirth; if (b != null) Console.WriteLine($"Baby is called: { b.BabyCalled() }"); } } }
|
1 2
| Baby is called: kitten Baby is called: puppy
|