此篇博客目的是希望对于 C# 语言中面上对象的知识了解匮乏或者理解欠缺的读者在阅读以后设计模式相关博客或者书籍能够更加轻松,故此篇博客只是简单介绍了 C# 语言中面向对象的的相关知识。
类与实例
对象是一个自包含的实体,用一组可识别的特性和行为来标识。
类是具有相同属性和功能的对象的抽象的集合,用 class 定义。
注意:
- 类名称首字母大写,多单词则各个首字母大写;
- 对外公开的方法要用 public 修饰符。
实例就是一个真实的对象。
实例化创建对象的过程,使用 new 关键字创建。
构造方法
构造方法又叫构造函数,对类进行初始化。构造方法和类同名,无返回值,也不需要 void,在 new 的时候调用。
所有的类都有构造方法,若不编码则系统默认生成空的构造方法,若有定义的构造方法,则默认的构造方法时效。
方法重载
方法重载提供了创建同名的多个方法的能力,但这些方法需要使用不同的参数类型。
注意:
方法重载时,两个方法的方法名必须相同,但是参数类型或者个数可以不同。
方法重载可在不改变原方法的基础上新增功能。
属性与修饰符
属性是一个方法或者一对方法,从调用代码来看的话,它是一个字段。
属性的两个方法:
- get
get 访问器返回与声明的属性相同的数据类型,表示的意思是调用时可以得到内部字段的值或者引用。 - set
set 访问器没有显示设置参数,但其有一个隐式参数,用关键字 value 表示,作用是调用属性石可以给内部的字段或引用赋值。
字段是存储类要满足其设计所需要的数据,是与类相关的变量。
属性适合于以字段的方式使用方法调用的场合。
修饰符 用于修改类型和类型成员的声明,参见 微软文档 以及 C# 访问修饰符。
- 字段都是 private,即私有的变量,修饰的类成员只允许同一个类中的成员访问;
- 属性都是 public,即公有的变量,修饰的类成员可以允许其他任何类来访问。
封装
封装每个对象都包含它能进行操作所需要的所有信息的特性,这个特性叫做封装。
封装的好处:
- 能够减少耦合;
- 类内部的实现可以自由修改;
- 类具有清晰的对外接口。
继承
对象的继承代表了一种「is-a」的关系,如果两个对象 A 和 B,可以描述为「B 是 A」,则表明 B 可以继承 A。
继承者可以理解为是对继承者的特殊化,因为它除了具备被继承者之外的特性外,还具备自己独有的个性。
继承定义了类如何相互关联,共享特性。
继承的工作方式是,定义父类和子类,或叫做基类和派生类,其中子类继承父类所有特性。子类不但继承父类所有特性,还定义了新的特性。
若子类继承于父类:
- 子类拥有父类非 private 的属性功能;
- 子类有自己的属性和功能(子类可以扩展父类没有的属性和功能);
- 子类还可以以自己的方式实现父类的功能(方法重写)。
子类从它的父类中继承的成员有方法、域、属性、时间、索引指示器,但对于构造方法来说,其不能被继承,只能被调用。对于调用父类的成员,可以用 base 关键字。
继承的优点: 继承使得所有子类公共的部分都放在了父类,使得代码得到了共享,避免了重复,另外,继承使得修改或扩展而来的实现都比较容易。
继承的缺点: 父类变的话,子类必须变;继承会破坏包装,父类实现细节暴露给子类。
多态
多态表示不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行。
注意:
- 子类以父类的身份出现;
- 子类在工作时以自己方式实现;
- 子类以父类身份出现时,子类特有属性和方法不可以使用。
虚方法: 为了使子类的实例完全接替来自父类的类成员,父类必须将该成员的声明为虚拟的。通过在该成员的返回类型之前添加 virtual 关键字实现。
重写: 子类可以选择使用 override 关键字,将父类实现替换为它自己的实现,这就是方法重写 Override,或者叫做方法覆写。
抽象类
C# 中允许把类和方法声明为 abstract,即抽象类和抽象方法。
注意:
- 抽象类不可以实例化;
- 抽象方法是必须被子类重写的方法;
- 若类中包含抽象方法,就必须定义为抽象类,不论是否包含一般方法。
抽象类通常代表一个抽象概念,提供了一个继承的出发点,,当设计一个新的抽象类时,一定是用来继承的,所以,在一个以继承关系形成的等级结构中,树叶节点应当是具体类,而树枝节点均应当是抽象类。
推荐阅读:抽象类与抽象方法。
接口
接口 是把隐式公共方法和属性组合起来,以封装特定功能的一个集合,一旦类实现了接口,类就可以支持接口所指定的所有属性和成员。
声明接口在语法上和声明抽象类完全相同,但是不允许提供接口中任何成员的执行方式。故:
- 接口不能实例化;
- 不能有构造方法和字段;
- 不能有修饰符;
- 不能声明虚拟的或静态的等。
注意:
- 一个类可以支持多个接口,多个类可以支持相同接口;
- 接口在命名时,前面要加一个大写字母「I」;
- 接口用 interface 声明;
- 接口名称之间加 「I」;
- 接口的方法或属性前面不可以有修饰符、方法没有方法体。
有关抽象类和接口的区别,请参考:抽象类和接口的区别
集合
推荐阅读:C# 数组
.NET Framework 提供了用于数据存储和检索的专用类,这些类称为集合。这些类提供对堆栈、队列、列表和哈希表的支持。大多数集合类实现相同的接口。
优点:
- 可以根据使用大小按需动态增加,不用受事先设置其大小的控制;
- 可以随意地添加、插入或移除某一范围元素,比数组要方便。
缺点:
-
ArrayList 不是类型安全的
ArrayList不管你是什么对象都是接受的,因为在它眼里所有元素都是 Object,这就使得如果你「 iarrayAnimal.Add(123)」;或者「arrayAnimal.Add(“HelloWorld”);」
在编译时都是没有问题的,但在执行时,「foreach(Animal item in arrayAnimal)」需要明确集合中的元素是Animal类型,而123是整型,HelloWorld是字符型,这就会在运行到此处时报错,显然,这是典型的类型不匹配错误。 -
ArrayList 对于存放值类型的数据,比如 int、string 型或者结构 struct 的数据,用 ArrayList 就意味着都需要将值类型装箱为 Object 对象,使用集合元素时,还需要执行拆箱操作,这就带来了很大的性能损耗。
装箱就是把值类型打包到Object 引用类型的一个实例中。比如整形变量 i 被「装箱」并赋值给对象 o。
int i = 123;
object o = (object) i ;//装箱
拆箱就是指从对象中提取值类型。对象 o 拆箱并将其赋值给整形变量「i」。
o = 123;
i = (int) o ;//拆箱
泛型
泛型是具有占位符(类型参数)的类、结构、接口和方法,这些占位符是类、结构、接口和方法所存储或使用的一个或多个类型的占位符。
泛型集合类可以将类型参数用作它所存储的对象的类型的占位符;类型参数作为其字段的类型和其方法的参数类型出现。
通常情况下,都建议使用泛型集合,因为这样可以获得类型安全的直接优点而不需要从基集合类型派生并实现类型特定的成员。此外,如果集合元素为值类型,泛型集合类型的性能通常优于对应的非泛型集合类型(并优于从非泛型基集合类型派生的类型),因为使用泛型时不必对元素进行装箱。
委托与事件
委托是对函数的封装,可以当做给方法的特征指定一个名称,委托对象用 delegate 来声明。
注意:
委托是一种引用方法的类型,一旦为委托分配了方法,委托将于该方法具有完全相同的行为
事件是委托的一种特殊形式,当发生有意义的事情时,时间对象处理通知过程,事件对象用 event 关键字声明。
注意:
事件是说在发生其他类或对象关注的事情时,类或者对象可通过事件通知他们。
注:本博客内容来自《大话设计模式》,作者程杰,清华大学出版社出版。