C#学习笔记 -- 接口
1、什么是接口
-
接口指定一组函数成员, 而不实现他们的引用类型
-
只能类和结构来实现接口
例子
//声明实现接口的CA类
class CA : IInfo
{
public string Name;
public int Age;
//在类中实现接口的方法
public string GetAge()
{
return Age.ToString();
}
public string GetName()
{
return Name;
}
}
class CB : IInfo
{
public string First;
public string Last;
public double PersonsAge;
public string GetAge()
{
return PersonsAge.ToString();
}
public string GetName()
{
return First + "·" + Last;
}
}
public interface IInfo
{
string GetName();
string GetAge();
}
public static class Printer
{
public static void PrintInfo(IInfo item)
{
Console.WriteLine($"名字: {item.GetName()}, 年龄: {item.GetAge()}");
}
}
static void Main(string[] args)
{
CA a = new CA() { Name = "John Doe", Age = 35 };
CB b = new CB() { First = "James", Last = " Doe", PersonsAge = 33 };
PrintInfo(a);
PrintInfo(b);
}
(1)使用IComparable接口
-
Array.Sort()
依赖于IComparable
的接口public interface IComparable { int CompareTo(Object obj); }
-
CompareTo()
方法返回值如下-
负数: 如果当前对象小于参数对象
-
正数: 如果当前对象大于参数对象
-
0: 两个对象相对
-
-
Sort()
使用的算法是依赖与使用对象的CompareTo()
来决定两个元素的次序,int
类型实现了IComparable
, 但是自定义类没有, 因此自定义类的数组无法使用Sort()
-
让类实现
IComparable
接口, 让Sort
方法可以用于自定义类型的对象
class MyClass : IComparable
{
public int TheValue;
//实现接口方法
public int CompareTo(object obj)
{
MyClass mc = (MyClass)obj;
return this.TheValue - mc.TheValue;
}
}
(2)实现一个接口, 类和接口必须要做的事情
-
在基类列表中列出接口名称
-
为接口的每个成员提供实现
2、声明接口
声明接口重要事项
-
接口可以使用任何访问修饰
public、protected、internal、private
-
接口成员是隐式
public
的, 不允许用任何访问修饰符, 包括public
-
接口声明不能包含以下成员
-
数据成员 (字段)
-
静态成员
-
-
接口声明只能包含如下类型的非静态成员函数的声明
-
方法
-
属性
-
事件
-
索引器
-
-
这些函数成员的声明不能包含任何实现代码, 必须使用分号代替每一个成员声明的主体
-
接口名称必须以大写的
I
开头 -
与类和结构一样, 接口声明也可以分割成分部接口声明
访问修饰符 interface IMyInterface1
{
//不能有字段和静态成员
//成员不能用任何访问修饰符
Type DoStuff(Type nVar1, Type lVar2);
}
3、实现接口
只有类和结构才能实现接口, 要实现接口, 必须有如下
-
在基类列表中包括接口名称
-
为每一个接口成员提供实现
注意
-
如果类实现了接口, 他必须实现接口所有成员
-
如果类派生自基类并实现了接口, 基类列表中的基类名称必须放在接口之前
class Derived : MyBaseClass, IIfc1, IIfc2
{
//...
}
4、接口是引用类型
-
不能直接通过类对象的成员访问接口
-
可以通过把类对象引用强制转换为接口类型来获取指向接口的引用
-
一旦有了接口的引用, 就可以使用
.
来调用接口的成员, 使用这个接口, 你不能调用不属于这个接口成员的类成员
class MyClass : IIfc1
{
public void PrintOut(string s)
{
//...
}
}
interface IIfc1
{
void PrintOut(string s);
}
static void Main(string[] args)
{
MyClass mc = new MyClass(); //创建类对象
mc.PrintOut("object");//调用对象的实现方法
IIfc1 ifc = (IIfc1)mc;//转换为接口类型的引用
ifc.PrintOut("iterface");//调用接口方法
}
5、接口和as运算符
可以使用强制转换运算符来获取对象接口的引用, 另一个更好的方式是as运算符
如果将类对象引用强制转换为类未实现的接口的引用, 强制转换操作为抛出异常, 可以使用as运算符来避免这个问题
接口名称 接口引用 = 类对象引用 as 接口名称
-
如果类实现了接口, 表达式返回指向接口的引用
-
如果类没有实现接口, 表达式返回null而不是抛出异常
ILiveBirth b = a as ILiveBirth;
if(b != null)
{
Console.WriteLine($"{b.BabyCalled()}");
}
6、实现多个接口
-
类或结构可以实现任意数量的接口
-
所有实现的接口必须列在基类列表中并以逗号分割(如果有基类名称, 则在其之后)
class MyData : IDataRetrieve, IDataStore
{
...
}
7、实现有重复成员的接口
由于类可以实现任意数量的接口, 有可能有两个或多个接口成员具有相同的签名和返回类型
interface IIfc1
{
void PrintOut(string s);
}
interface IIfc2
{
void PrintOut(string s);
}
-
如果一个类实现了多个接口, 并且其中一些接口成员具有相同的签名和返回类型
-
那么类可以实现单个成员来满足所有包含重复成员的接口
class MyClass : IIfc1, IIfc2
{
public void PrintOut(string s)
{
Console.WriteLine($"PrintOut:{s}");
}
}
static void Main(string[] args)
{
MyClass mc = new MyClass();
mc.Print("object");
}
8、多个接口的引用
-
可以通过对象强制转换为接口类型的引用, 来获取一个指向接口的引用
-
如果类实现了多个接口, 我们可以获取每个接口的独立引用
interface IIfc1
{
void PrintOut(string s);
}
interface IIfc2
{
void PrintOut(string s);
}
class MyClass : IIfc1, IIfc2
{
public void PrintOut(string s)
{
Console.WriteLine($"s = {s}");
}
}
static void Main(string[] args)
{
MyClass mc = new MyClass();
IIfc1 ifc1 = (IIfc1) mc;
IIfc2 ifc2 = (IIfc2) mc;
mc.PrintOut("object");
ifc1.PrintOut("Interface1");
ifc2.PrintOut("Interface2");
}
9、派生成员作为实现
实现接口的类可以从它的基类继承实现的代码
interface IIfc1
{
void PrintOut(string s);
}
class MyBaseClass
{
public void PrintOut(string s)
{
Console.WriteLine($"{s}");
}
}
class Derived : MyBaseClass, IIfc1
{
}
static void Main(string[] args)
{
Derived derived = new Derived();
derived.PrintOut("derived");//dervied
}
-
父类实现了某个接口的方法
-
子类继承父类, 并且实现接口
-
子类中不实现接口方法, 就可以调用
10、显示接口成员实现
-
为每一个接口分离实现, 可以创建显示接口成员
-
与所有接口实现想死, 位于实现了接口的类或结构中
-
使用限定接口名称来声明, 由接口名称和成员名称以及他们中间的点来分隔符号构成
访问修饰符 返回类型 接口.方法(参数列表)
{
实现
}
interface IIfc1
{
void PrintOut(string t);
}
interface IIfc2
{
void PrintOut(string s);
}
class MyClass1610 : IIfc1, IIfc2
{
void IIfc1.PrintOut(string t)
{
Console.WriteLine($"IIfc1: {t}");
}
void IIfc2.PrintOut(string s)
{
Console.WriteLine($"IIfc2: {s}");
}
}
static void Main(string[] args)
{
MyClass1610 mc = new MyClass1610();
Interface1610.IIfc1 iifc1 = (Interface1610.IIfc1) mc;
iifc1.PrintOut("iifc1");
Interface1610.IIfc2 iifc2 = (Interface1610.IIfc2) mc;
iifc1.PrintOut("iifc2");
}
注意
-
在接口方法没有指向类级别实现, 而是包含了自己的代码
-
不能使用
MyClass1610
的对象来调用方法, 不存在类级别的Printout
方法
如果有显示接口成员实现, 类级别的实现是允许的, 担不是必须的, 显示实现满足了类火结构必须实现方法的需求, 因此, 可以有如下三种实现场景
-
类级别实现
-
显示接口成员实现
-
类级别和显示接口成员实现
访问接口成员实现
显示接口成员只能通过接口指向的引用来访问, 其他的类成员都不可用直接访问他们, 必须转换为接口引用
class MyClass1610 : IIfc1
{
void IIfc1.PrintOut(string t)
{
Console.WriteLine($"IIfc1: {t}");
}
public void Method1()
{
//编译错误
//PrintOut("x");
//this.PrintOut("x");
//转换为接口引用
((IIfc1)this).PrintOut("xxx");
}
}
这个限制对继承产生类重要的影响, 由于其他类成员不能直接访问显示接口成员实现, 派生类的成员也不能直接访问他们
11、接口可以继承接口
-
要指定某个接口继承其他接口, 形式如下
interface 派生接口 : 基接口1, 基接口2 { ... }
-
类的基类只能有一个, 而接口可以有多个任意基接口
-
列表中的接口本身可以继承其他接口
-
结果接口包含它声明的所有成员和基接口的所有成员
-
interface IDataRetrieve
{
int GetData();
}
interface IDataStore
{
void SetData(int x);
}
interface IData : IDataRetrieve, IDataStore
{
}
class MyData : IData
{
int nPrivateData;
public int GetData()
{
return nPrivateData;
}
public void SetData(int x)
{
nPrivateData = x;
}
}
static void Main(string[] args)
{
MyData myData = new MyData();
myData.SetData(5);
Console.WriteLine($"{myData.GetData()}");
}
12、不同类实现一个接口
interface ILiveBirth
{
string BabyCalled();
}
class Animal
{
}
class Dog : Animal, ILiveBirth
{
public string BabyCalled()
{
return "puppy";
}
}
class Cat : Animal, ILiveBirth
{
public string BabyCalled()
{
return "pussy";
}
}