C#入门学习笔记 #9(析构器、类声明、访问控制、继承、重写、多态、抽象类、开闭原则)
欢迎进入这篇文章,文章内容为学习C#过程中做的笔记,可能有些内容的逻辑衔接不是很连贯,但还是决定分享出来,由衷的希望可以帮助到你。
笔记内容会持续更新~~
析构器
在创建对象的时候,可以用构造器做一些事情,当对象在内存中没有任何变量引用它的时候,这个对象就不能够再被访问了,这个时候垃圾收集器就会视这块内存为垃圾内存,就会把这块垃圾内存收回,而收回的过程当中如果想让它做什么事情,就放在析构器里就行了,下面通过一段代码来介绍。
类声明
类可以声明在名称空间内,可以将类声明在所有显式名称空间之外,也就是Global名称空间里面,也可以声明在类体内,此时就被称为成员类。在实际项目当中,成员类还是非常多的。
class_declaration: attributes? class_modifier* 'partial'? 'class' identifiertype_parameter_list? class_base? type_parameter_constraints_clause*class_body ';'?;
以上是官方对类声明的描述,我们先来介绍class_modifier。以下是一系列修饰符。
class_modifier: 'new'| 'public'| 'protected'| 'internal'| 'private'| 'abstract'| 'sealed'| 'static'| unsafe_modifier // unsafe code support;
类访问控制
先介绍其中关于类的访问级别的,public和internal。
public
可以看到,如果没有使用public修饰符将Calculator类从MyLib中暴露出来,哪怕引用了MyLib,也无法看到Calculator类。如果不加public修饰符,其实默认是加了internal修饰符的。
internal
internal修饰符可以使在MyLib这个项目内可以自由访问Calculator类。也就是说internal修饰符把类的访问级别限制在了项目级别。
注意:这里可能会想,“能否使用private” ,是不可以的。只有类在作为其他类的成员类的时候,才有可能会看见private class。
打出后没有提示且有报错。
继承
继承的本质,是派生类在基类已有成员的基础上对基类进行的横向和纵向上的扩展。横向扩展指的是对类成员的补充,纵向扩展对类成员的版本的更新,也就是对类成员的重写。
由下图代码运行结果可以看到,Car继承了Vehicle。一个类只能派生于一个基类。且子类的访问级别不能高于父类。
这里介绍一个很重要的概念:一个子类的实例,从语义上来说也是父类的一个实例。一个派生类的实例,从语义上来说也是基类的一个实例。用例子来说就是,Car的实例,也是Vehicle的实例。
sealed
这里顺便介绍下sealed修饰符,如果在一个类前加上sealed,那么这个类就不允许派生出去了。
下面我们继续介绍继承,子类对父类的成员全盘继承。
但是父类的实例构造器不能被子类继承。
讲了继承后我们继续介绍类的访问级别。
访问级别
首先是第一个原则,类成员的访问级别是以类的访问级别为上限的,不可能在另一个程序集中超过类的访问级别。
如上图,当Vehicle类的访问修饰符为internal而类成员为public时,以Vehicle类的访问级别为上限,在另一个程序集中不能跨级别访问。 反过来:
上面已经介绍过:internal修饰符可以使在MyLib这个项目内可以自由访问Calculator类。也就是说internal修饰符把类的访问级别限制在了项目级别。所以上图将Owner成员用internal修饰符修饰后,在其它程序集里就访问不到Owner成员,即使Vehicle可以访问到。
private
这个修饰符更狠,它会将成员的范围限制在当前类的类体中。只能继承下来,但是不能访问。
这里如果不加任何修饰符,默认的就是private,起到保护数据的作用。
protected
protected会把类成员的访问级别限制在继承链上,就是当父类当中有一个类成员使用protected修饰后,它所有的子类都是可以访问这个成员的,但是不在这个继承链上的其他成员不可以访问。
重写
重写也算是继承的范畴,上面介绍的是继承的横向扩展,增加了类成员的数量,重写呢,则是在纵向扩展,子类在继承了父类之后呢并没有增加类成员的数量,而是重写了父类成员的功能。下面通过代码来介绍:
此时Run方法并没有被重写,重写需要在父方法加上virtual,在子方法中加上override。
此时,Run方法就被重写了。
多态
接下来介绍下多态的概念,使用父类变量引用子类实例。
抽象类
抽象类和接口是高阶面向对象设计的起点,后面再介绍接口。这里先介绍抽象类,有一个知识叫设计模式,要学习设计模式有两个重要前提,第一个前提是熟练掌握抽象类和接口的使用,第二个前提就是要深入理解solid设计原则(Single Responsibility Principle、Open/Closed Principle、Liskov Substitution Principle、Interface Segregation Principle、Dependency Inversion Principle)。介绍抽象类。
当一个类的类成员或者类方法为抽象的,即使用abstract 修饰,类也需要使用abstract修饰,如此就完成了抽象类的声明。注意这里被abstract修饰的类方法,只有返回值、方法名和参数列表,没有方法体,即花括号,是个没有被完全实现的方法。抽象类中必须至少有一个这样的没有被完全实现的方法。抽象类是不允许被实例化的。不允许被实例化,就只有两个用处,一是作为基类,让别的类从它这里派生出去,第二呢就是作为基类去声明变量,用基类类型的变量引用一个子类类型的实例,子类中呢实现了抽象类中没有实现的方法。
开闭原则
如果不是为了修bug或者是添加新功能,不要老是去修改一个类的代码,特别是这个类当中的函数成员的代码。即封装那些不变的稳定的成员,把不确定的成员声明为抽象成员,留给子类去具体实现,这里就提到了抽象成员,因此这二者是密不可分的。下面就通过一段代码,来理解下抽象类和开闭原则。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApp3{internal class Program{static void Main(string[] args){Vehicle v = new Car();v.Run();}}abstract class IVehicle{public abstract void Run();public abstract void Stop();public abstract void Fill();}abstract class Vehicle : IVehicle{public override void Stop(){Console.WriteLine("Stop!");}public override void Fill(){Console.WriteLine("Pay and fill...");}}class Car : Vehicle{public override void Run(){Console.WriteLine("Car is runing!");}}class Truck : Vehicle{public override void Run(){Console.WriteLine("Truck is runing!");}}
}
这段代码中有一个纯抽象类:IVehicle,然后一步一步由抽象转为具体。在C++代码中经常使用这种写法,但是在Java和C#里,这种纯抽象类就是接口。因此代码可以做出如下改变。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApp3{internal class Program{static void Main(string[] args){Vehicle v = new Car();v.Run();}}interface IVehicle{void Run();void Stop();void Fill();}abstract class Vehicle : IVehicle{public void Stop(){Console.WriteLine("Stop!");}public void Fill(){Console.WriteLine("Pay and fill...");}public abstract void Run();}class Car : Vehicle{public override void Run(){Console.WriteLine("Car is runing!");}}class Truck : Vehicle{public override void Run(){Console.WriteLine("Truck is runing!");}}
}
这里先使用了接口,后续将详细介绍接口。