过渡到C#的高级特性,本次主要陈述一下
接口的相关知识点。主要涉及到:
1、接口的定义和实现,以及
理解构建支持多种行为的类型有哪些优势。
2、接口引用、显示接口实现以及接口层次结构的构建。
3、还会涉及到一些.net基础类库中的标准接口。我们可以看到
自定义类和结构完全可以实现这些预定义的接口,以支持对象克隆、对象
枚举和对象排序等高级行为。
一、接口类型
接口就是一组抽象成员的集合。之前说过,抽象方法是纯粹的
协议,在其中没有提供默认的实现。(接口定义成员模拟行为)
tips:一个类可以支持任意数量的接口,因此也就只吃了多种行为。
接口定义形式如下:
public interface Iinterface
{
void OnTouchMove();//抽象成员
}
1、接口和抽象基类的区别。
接口和抽象基类很类似。如果类被标记为抽象的,他可以定义许多抽象成员来为所有派生类型提供多态接口。
区别一,类完全还可以定义许多
构造函数,字段数据和非抽象成员(具有实现)等。但是接口只能包含抽象成员。
区别二,抽象类中接口如果想要实现,子类必须继承。而一个子类只能有一个父类,所以子类不能再继承另一个抽象类中接口(方法)。但一个类可以有任意数量接口,也就是说可以通过接口扩展许多行为。
区别三,传统抽象基类的另一个
限制就是每一个派生类型必须处理这一组抽象成员,并且提供实现。这就使得子类有可能实现了许多冗余的方法。
二、创建自定义接口
接口使用C#
关键字interface来定义:
注意:我们定义接口成员,不需要为这个成员定义实现作用域。因为接口是纯粹的协议,如果我们定义实现,会产生编译错误。
不过,.net接口还可以定义许多属性协议,我们更新上面接口如下:
接口除非被类或接口实现,否则接口没有什么用,接下来,我们将讲述一下接口的实现。
三、实现接口
如果类(或结构)选择通过支持接口来扩展功能,就需要在其类型定义中使用逗号分隔的列表。要知道,直接基类必须是
冒号caozuofu.html" target="_blank">操作符后的第一个项。
如:
public
class Pencil:Ipointy
{...}
public class Fork:Utensil,Ipointy
{....}
注意:实现接口是一个“要么全要,要么全部要”的命题,也就是说支持类型无法选择实现哪些成员。
例如:
class Triangle : IPointy //在此处,右键点击实现接口,则会自动将接口中的内容实现。修改实现体就可以达到我们想要实现的效果。(如果实现多个接口并且有重复的名字的成员,可以通过显式实现接口解决。)
{
public byte Ponits
{
get { return 6; }
}
public byte GetNumberOfPoints()
{
Console.WriteLine("Interface!");
return 0;
}
}
四、在对象级别调用接口成员
Triangle 类已经实现了IPointy 接口,有了Points属性,用法如下:
运行结果如下:
这种情况下,是没有问题的,但是如果一个类没有实现接口,调用Points属性的时候,就会出现编译错误。
因此,有的时候就需要判断一个类型支持哪些接口,就用到了下面的方法:
1、try-catch逻辑,接口强制转换中如果不支持这个接口,就会出现无效转换
异常(InvalidCastException)。(不是最好的)
2、获取接口引用:as关键字。
例如新的类型A判断则可以如下:
A a=new A(); IPointy ips= a as IPointy ;//接下来判断ips是不是为空,然后输出其Points属性。
3、获取接口引用:is关键字
用法:
if(a is IPointy ){Console.Write((IPointy)a.Points);}
else //...
五、接口作为参数
用法如下:
六、接口作为返回值
接口也可以用作方法的返回值。用法如下:
结果:
七、接口类型数组
同样的类型可以由多个类型实现,即使这些类型不处于相同的类层次结构中,并且除System.Object以外,也没有其他的公共父类。这可以产生出许多非常强大的编程结构。
如果已经定义了A,B,C类型,那么现在可以定义一个支持IPointy接口的对象数组:
IPointy[] myPionty = {new A(), new B(), new C()};
foreach(IPointy i in myPointy){Console.Write(i.Ponits);}
八、接口的层次结构
接口可以组织成接口层次结构,和类层次结构相似,如果接口扩展了既有接口,它就继承了父接口定义的抽象成员,当然,和基于类的继承不同的是,派生接口不会继承真正的实现,而是通过额外的抽象成员扩展了其自身的定义。
和类不同,一个接口可以扩展多个基接口,这就允许我们设计非常强大、非常灵活的抽象。接口在以下两个方面特别有用:
1、只有一个层次结构,但是只有一部分派生类支持某个公共行为。
2、需要构建的公共行为跨多个层次结构,而且除了System.Object以外,没有其他的公共父类。
九、构建可枚举类型(IEnumerable和IEnumerator)
我们知道,IEnumerable和IEnumerator的作用是使得C#支持foreach关键字,允许我们遍历任何数组类型的内容。
虽然,看上去只有数组类型才可以使用这个结构,其实任何支持GetEnumerator()方法的类型都可以通过foreach结构进行运算。
看如下代码:
执行结果:
出现如此错误的原因是因为我们的类不是实现名为GetEnumerator()的方法。
将TheFather类进行如下更改,则编译安全通过:
在以前,如果我们希望构建支持foreach枚举的自定义集合,只能实现IEnumerable接口,可能还有IEnumerator接口,然而还可以通过迭代器来构建使用foreach
循环的类型。
1、用yield关键字构建迭代器方法。
简单来说,迭代器就是这样一个成员方法,它制定了容器内部项被foreach处理时该如何返回。虽然迭代器方法还必须命名为GetEnumerator方法,返回值还必须是IEnumerator类型,但自定义类不需要实现原来那些接口了。
修改TheFather类如下,将会实现和上述一样的功能,编译依旧可以安全通过:
yield关键字用来向调用方的foreach结构指定返回值。当到达yield return语句后,当前位置被存储下来,下次调用迭代器会从这个位置开始执行。
2、构建命名迭代器
更有趣的是,yield关键字从技术上说可以
结合任
何方法一起使用,无论方法名是什么。这就是命名迭代器技术。它的好处在于可以接受许多参数。
注意,这些方法返回IEnumerable接口,而不是期望的IEnumerator兼容类型。
修改TheFather类如下:
同时需要修改主函数内的迭代:
3、迭代器方法的内部表示
如果C#编译器遇到迭代器方法,它就会在定义类型的作用域内动态生成嵌套类。记住,如果自定义类型要和C#的foreach关键字一起使用,容器就需要定义一个名为GetEnumerator的方法,它由IEnumerable接口类型来定制。通常,这个方法的实现只是交给保存子对象的内部成员,然而,我们也可以使用yield return语法来提供多个“命名迭代器”方法。
十、构建可克隆的对象(ICloneable)
之前提到过,System.Object定义了一个名为MemberwiseClone的成员。这个方法用来获取当前对象的一份浅复制。因为它是受保护的,对象用户不会直接调用到这个方法,而一个对象可能在克隆过程中自己调用这个方法。
之前还提到的引用类型和
值类型里面有,如果给一个引用变量分配另一个引用变量,将有两个引用指向
内存中同一个对象,对其中一个的操作,将会将两个引用全部改变。
如果想要使自己自定义类型支持向调用方法返回自身同样副本的能力,需要实现标准的ICloneable接口。
使用方法:
class A:ICloneable
{
public object Clone(){return new A();}//实现的Clone方法返回当前对象的一个副本。
}
用的时候:
static void Main()
{
A a = new A();
A b = (A)a.Clone();//这种情况下,当修改b内的成员的时候,a内的成员不会被改变。
}
对于更深层次的复制,在这里不做过多陈述。
十一、构建可比较的对象
System.IComparable接口指定了一种允许一个对象可基于某些特定键值进行排序的行为。接口正式定义:
public interface IComparable
{
int CompareTo(object o);
}
代码如下:
构建自定义类型的时候,可以实现IComparable以使得该类型数组可被排序,充实CompareTo的细节时候,需要决定排序操作的基准。
修改代码如下:
下面是main函数的代码:
运行结果:
除此之外,我们还一颗指定多个排序顺序,以及自定义属性和自定义排序类型。在此不再陈述。
十二、小结
接口可以被定义为抽象成员的集合。因为接口不提供任何实现细节,通常把接口看做某个类型支持的行为。
C#提供了interface关键字来允许我们创建接口,。并且类可以实现多个接口,中间通过逗号进行分割。
除此之外,我们还陈述了几个标准接口的实现,以获得诸如克隆排序和枚举的功能。