接口概述

接口是指定一组函数成员而不实现它们的引用类型。所以只能类和结构来实现接口。

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();
}
//声明实现接口的CA类
class CA : IInfo
{
public string Name;
public int Age;
//在CA类中实现两个接口方法
public string GetName() { return Name; }
public string GetAge() { return Age.ToString(); }
}
//声明实现接口的CB类
class CB : IInfo
{
public string First;
public string Last;
public double PersonsAge;
//在CB类中实现两个接口方法
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]; //创建MyClass对象的数组
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 );
}

接口的访问性和接口成员的访问性之间有一些重要区别。

  • 接口声明可以有任何的访问修饰符:publicprotectedinternalprivate
  • 然而,接口成员是隐式 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; //跟 cast: (ILiveBirth)a 一样
↑ ↑
接口引用 运算符

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() //Main
{
MyData data = new MyData();
data.SetData( 5 );
Console.WriteLine($"Value = { data.GetData() }");
}
}
1
Value = 5

实现具有重复成员的接口

如果一个类实现了多个接口,并且其中一些接口成员具有相同的签名和返回类型,那么类可以实现单个成员来满足所有包含重复成员的接口。

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
Calling through:  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; //获取 IIfc1 的引用
IIfc2 ifc2 = (IIfc2) mc; //获取 IIfc2 的引用

mc.PrintOut("object"); //从类对象调用

ifc1.PrintOut("interface 1");//从 IIfc1 调用
ifc2.PrintOut("interface 2");//从 IIfc2 调用
}
}

派生成员作为实现

实现接口的类可以从它的基类继承实现的代码。

  • 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; //获取IIfc1的引用
ifc1.PrintOut("interface 1"); //调用显式实现

IIfc2 ifc2 = (IIfc2) mc; //获取IIfc2的引用
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 { } //基类 Animal

class Cat : Animal, ILiveBirth //声明 Cat 类
{
string ILiveBirth.BabyCalled()
{ return "kitten"; }
}

class Dog : Animal, ILiveBirth //声明 Dog 类
{
string ILiveBirth.BabyCalled()
{ return "puppy"; }
}

class Bird : Animal //声明 Bird 类
{
}

class Program
{
static void Main()
{
Animal[] animalArray = new Animal[3]; //创建 Animal 数组
animalArray[0] = new Cat(); //插入 Cat 类对象
animalArray[1] = new Bird(); //插入 Bird 类对象
animalArray[2] = new Dog(); //插入 Dog 类对象
foreach( Animal a in animalArray ) //在数组中循环
{
ILiveBirth b = a as ILiveBirth; //如果实现 ILiveBirth……
if (b != null)
Console.WriteLine($"Baby is called: { b.BabyCalled() }");
}
}
}
1
2
Baby is called: kitten
Baby is called: puppy