一、类
1.0对象初始化器
class Student
{
public String name;
public int age { get; set; }
}
internal class Program
{
static void Main(string[] args)
{ //写法一
Student stu=new Student();
stu.name="Tom";
stu.age=20;
//写法二
Student stu2 = new Student { name = "Tom", age = 20 };
在 C# 中, Student stu2 = new Student { name = "Tom", age = 20 }中的 {}
称为对象初始化器(Object Initializer),它是 C# 3.0 引入的语法糖,用于在创建对象时使用键对值方式直接初始化对象的公共属性或公共字段,简化代码书写,等同于写法一;
1.1构造器(构造函数)
特点:
与类同名,没有返回值
不自己定义系统会生成一个默认的构造器,就是将给成员属性赋该类型默认值
自己定义默认构造器会被覆盖,而不是重载(因为无法再调用系统自动生成的构造器)
1.1.1实例构造器和静态构造器
静态构造器不能有修饰限定符
静态构造器不能有参数
静态构造器不能重载
class student
{
public int score { get; set; }
public static int num { get; set; }
//实例构造器
public student(int score)
{
this.score = score;
num++;
}
//静态构造器
static student(){
num = 100;
}
}
1.1.2实例构造器和静态构造器的调用时机
实例构造器
- 每次使用
new
关键字创建类的实例时,必然会调用实例构造器(如果有多个重载,会根据new 后的参数匹配对应的构造器)。 - 若类有继承关系,创建派生类实例时,会先调用基类的实例构造器,再调用派生类的实例构造器(确保基类的实例成员先初始化)
静态构造器
静态构造器是类被首次 “访问” 时自动调用的方法,用于初始化类的静态成员(静态字段、静态属性等),与实例无关
- 首次创建该类的实例(
new
实例时)。 - 首次访问该类的静态成员(静态字段、静态属性、静态方法)。
- 首次使用该类作为泛型类型参数(如
List<MyClass>
首次被使用时,若MyClass
未初始化则触发其静态构造器 - 类有继承关系,基类的静态构造器先于派生类的静态构造器执行
1.2析构器(析构函数)
不加修饰限定符,使用(~类名)方式来定义函数
c#有资源回收器(GC),但是调用底层资源时有用
2、类的声明
c#类的声明和定义不分家
声明位置
- 把类声明在名称空间中
- 也可以把类声明在文件的名称空间外,但是c#有一个global名称空间,因此也在名称空间内部(不推荐)
- 可以声明在类中(成员类)
3、类的继承和类成员的访问控制
“是一个”规则:父类引用变量可以存储子类实例的地址,含义为:car是一个vehicle
static void Main(string[] args)
{
vehicle v = new car();
object o = new vehicle();
object o1 = new car();
}
}
class vehicle
{
}
class car : vehicle
{
}
- sealed关键字:封闭类,不能当作基类来使用,下例:编译器报错
- 一个类只能继承一个基类
- 一个类可以实现多个基接口
- 子类的访问级别不能超过基类
- 父类的构造器是不能被继承的
3.2编译器的调用规则
当创建子类实例时,父类的构造器先被启动,作用是初始化子类中继承的属性的值,下例中我们可以看到car中没有对owner进行赋值,但是任然可以输出值vehicle,而如果子类构造器对他赋值了,那么会覆盖这个结果。
static void Main(string[] args)
{
car mycar = new car();
Console.WriteLine(mycar.owner);
}
}
class vehicle
{
public vehicle() {
this.owner = "vehilce";
}
public string owner { get; set; }
}
class car : vehicle
{
public car()
{ }
}
上例是父与子类构造函数无需参数情况,需要参数时需要使用base关键字,下例
class vehicle
{
public vehicle(string own) {
this.owner = own;
}
public string owner { get; set; }
}
class car : vehicle
{
public car(string own):base(own)
{
}
}
3.3类的访问级别和类成员的关系
类成员的访问级别的上限是类的访问级别
类的默认访问级别是internal,成员的默认访问级别是private
protected:是可以跨程序集的,也就是说实际上,四种访问级别并不是严格的大小关系
3.4类的重写和多态
重写就是对父类成员的版本更新
重写需要父类成员对子类是可见的,即不能为private
需要重写的父类类成员(字段除外),要加关键字virtual(标识为可重写),子类类成员要加override
注意:我们使用父类的引用变量指向了子类的实例,这个方法调用的版本是由子类实例来决定的,在这时调用的重写的方法是继承链上最新的(到这个类),这就是多态,下例中将car的实例给到父类的引用变量,调用的是继承链到car的最新函数。
static void Main(string[] args)
{
vehicle c = new car();
c.run();
//运行结果为:I am driving
}
}
class vehicle
{
public virtual void run()
{
Console.WriteLine("I am running");
}
}
class car : vehicle
{
public override void run()
{
Console.WriteLine("I am driving");
}
}
class racecar : car
{
public override void run()
{
Console.WriteLine("I am racing");
}
}
3.5类的组合
在类的继承中,我们提到了”是一个“原则,即car是子类,vehicle是父类,那么car是一个vehicle,同时父类引用变量可以指向子类的实例;类的组合是”有一个“原则,一个类完全可以将另一个类作为自己的属性。
举个栗子:
class people {
public Address address { get; set; }
}
class Address {
public string city { get; set; }
}
internal class Program
{
static void Main(string[] args)
{ //分别实例化两个类
people p=new people();
Address a = new Address { city = "New York" };
p.address = a;
Console.WriteLine(p.address.city);
//简化写法
people p2 = new people { address=new Address { city="AAA" } };
Console.WriteLine(p2.address.city);
}
}
上面代码定义了两个类,people这个类直接使用Address作为他的属性,在我们给people这个类赋值的时候,有两种方式:
方式一:分别实例化两个类,将a这个Address类的引用变量赋值给p.address。
方式二:直接嵌套两个初始化器,在people的初始化器中给address创建一个实例,并且也使用初始化器。
二、抽象类和开闭原则
抽象类
含有抽象方法的类叫做抽象类(只要有一个就行),抽象类和抽象方法要用abstract来修饰,抽象类不能被实例化
抽象方法是指没有方法体的方法,抽象方法不能是private的,没有意义
开闭原则
应该将固定的,稳定的成员封装,不确定的成员应该声明为抽象成员,留给子类实现
实例:
- 类和方法都要加abstract
- 对抽象方法的重新也要加override
抽象类无法创建实例
abstract class vehicle
{
public abstract void run();
}
class car : vehicle
{
public override void run()
{
Console.WriteLine("car am driving");
}
}
三、接口
- 接口使用interface来定义,类似于类的class,接口本身的访问修饰符是可以定义的
- 接口的成员的访问修饰符固定为public的,不需要写,也不允许写
- 在抽象类和抽象函数中,我们需要加上abstract来修饰这是一个抽象类(函数),但是接口不能写,直接就是最简单的函数写法,下例
- 接口的方法不行全部被实现,如果一个类实现了接口但是没有实现全部方法,也就是说内部有抽象方法,这个类是抽象类(符合定义),在继承链上想要实例化一个类,那么这个类肯定是这个抽象类的子孙类
interface Iphone
{
void Dail();
void Pickup();
void Send();
void Receive();
}
class Nokia : Iphone
{
public void Dail()
{
Console.WriteLine("Nokia is dailing");
}
public void Pickup()
{
Console.WriteLine("Nokia is picking up");
}
public void Receive()
{
Console.WriteLine("Nokia is receiving");
}
public void Send()
{
Console.WriteLine("Nokia is sending");
}
}
class userphone
{
private Iphone _phone;
public userphone(Iphone phone)
{
_phone = phone;
}
public void Usephone() {
_phone.Dail();
_phone.Pickup();
_phone.Send();
_phone.Receive();
}
}
internal class Program
{
static void Main(string[] args)
{
userphone user = new userphone(new Nokia());
}
}
在这个例子中,我们可以看到虽然无法创建接口的实例,但是可以创建接口类型的引用变量指向子类实例实现多态,在userphone中调用Nokia类中函数
如果再有一个另外品牌的手机我们只需要修改传入的实例即可,例如:(new huawei()),这样我们可以通过
3.2依赖反转
由这张图解释上面代码
不使用接口时:
例如:有两个品牌的手机(Nokia和huawei)需要创建两个类(这一步都一样),手机的使用者(userphone)需要使用不同的手机(在这个类中调用手机类的方法)那么要不然就是创建两个(userphone)类似的类分别实例化,要不只能在一个类中写两个不同名的方法(不同点1),这时,手机和手机的使用者,是手机使用者依赖于手机,因为需要在手机使用者方法(Usephone)中实例化手机类才能调用手机中的方法(不同点2)对应图中左上角
使用接口时:
我们创建一个接口(Iphone),两个品牌的手机依赖于Iphone因为实现接口,使用者(userphone)依赖于(Iphone),这时依赖关系发生了变化,并且现在在使用者(userphone)中想要实现不同手机类中的方法,只需要使用接口类的引用类型,当实例时传入Nokia时调用Nokia的方法,传入huawei时调用huawei的方法
使用接口的优势:
当我们需要增加一个手机品牌例如三星,不使用接口时,除了写上三星类,我们需要修改手机使用者类,增加函数,或者直接专门写一个三星手机使用者的类,对应上图左上角
使用接口时:只需要写上三星类,然后调用时把三星类的实例作为实际参数传给使用者类中的函数的接口的引用变量就可以了(对应上图右上角)
在修改代码时不需要改变userphone类的成员,因为userphone不依赖于某个手机品牌的类,只依赖于接口,这样增加了类的独立性,我们称为解耦合