类的用法,C#和TS非常相似,UTS和TS基本保持一致。实际开发中,C#的所有代码都必须在类中,而TS的客户端开发,则很少会用到class,主要使用type或interface,为什么?正文解释。
一、类和对象
1.1 C#中定义类和创建对象
//创建一个类,有两个属性成员Name和Age
class Person
{
//***字段或属性如果没有初始化,框架自动赋默认值
//其中数值类型为0,布尔为false,字符为'\0',其它引用类型为null
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main()
{
// 实例化 Person 类,也叫创建对象
Person person1 = new Person();
person1.Name = "张三";
person1.Age = 25;
Console.WriteLine($"{person1.Name} 的年龄是 {person1.Age}");
}
}
/*C#中new创建实例的过程
1、分配内存空间来存储该对象
2、字段或属性初始化,如果没有显式赋值,则赋值相应类型的默认值
3、调用构造函数,如果有给字段或属性赋值,则会覆盖第2步的值
如果没有显式创建构造函数,系统会自动创建一个无参数构造函数,如果有,则不会自动创建议
*/
1.2 TS/UTS中定义类和创建对象
class Person {
//***TS不要求初始化,但UTS中要求必须初始化
name: string;
age: number = 0;
}
// 实例化 Person 类,也叫创建对象
let person1 = new Person();
person1.name = "张三";
person1.age = 25;
console.log(`${person1.name} 的年龄是 ${person1.age}`);
/*TS中new创建实例的过程
1、创建一个空对象
2、将这个对象的原型设置为类的原型对象(详见原型章节)
3、执行类的构造函数,将新创建的对象作为 this 上下文,并在构造函数中进行属性初始化
如果没有显式创建构造函数,系统会自动创建一个无参数构造函数,如果有,则不会自动创建议
*/
二、类的成员
2.1 C#的常量、字段、属性、方法和事件成员
2.1.1 类的可见修饰词
- public: 可以被任何代码访问,建议都加public
- internal: 只能在当前程序集内被访问,默认值
2.1.2 类的成员的可见修饰词
- public: 可以被任何代码访问
- protect internal:只能在类内部、派生类或当前程序保中被访问
- internal: 只能在当前程序集内被访问
- protect:只能在类的内部和派生类中被访问
- private: 只能在类的内部被访问,默认值
2.1.3 类的实例成员和静态成员
- 实例成员:类实例化后,才能访问和修改,数据保存在实例化后的对象中,正常创建的都是实例成员
- 静态成员:直接通过类名访问和修改,数据保存在类型元数据中,使用static修饰。实际上类的实例也可以访问静态成员,静态成员是被所有实例共享的
2.1.4 类的成员代码示例
//1、类的成员定义====================================================================
//本例中还少两个成员,构造函数和索引器。构造函数下节重点说,索引器极少用到。
public class MyClass
{
//1.1 常量字段(编译时)--------------------------------------------
public const int MYCONST1 = 10; //常量,具有静态特征,类和实例都能访问,全大写
public static const int MYCONST2 = 10; //静态常量,只能通过类访问,比较常用
//1.2 常量字段(运行时),使用readonly,在下节构造函数中说明----------
//1.3 字段---------------------------------------------------------
private int _myField1 = 10; //私有的实例字段,初始化,_小驼峰
public string myField2; //公有的实例字段,未初始化,默认值为null,小驼峰
public static int myField3 = 20; //公有的静态字段,初始化
//1.4 属性---------------------------------------------------------
//实例属性,完整格式,属性一般都是public,习惯用大驼峰
//属性本质上是方法,是对字段的封装,数据保存在后台字段上
//读取和修改属性时,实际上是调用get和set方法,每次都会调用
private int _myProperty1; //未初始化,默认值为0
public int MyProperty1
{
get { return _myProperty; }
set { _myProperty = value; }
}
public string MyProperty2 //简写方式①:方便写getter和setter语句
{
get => _myProperty2;
set => _myProperty2 = value;
}
public int MyProperty3{ get;set } //简写方式②:最简化
public int MyProperty4{ get; } = 10 //简写方式③:只读属性,并初始化
public int MyProperty5 { get; private set; } //简写方式④:只能在类的内部修改
public int MyProperty6 { get; init; } //简写方式⑤:只能在构造函数,即初始化时修改
public static int MyProperty7 { get; set; } = 5; //静态属性,并初始化
//1.5 方法---------------------------------------------------------
//实例方法,无参数,无返回值,习惯用大驼峰
public void MyMethod1()
{
Console.WriteLine("执行了特别的事情");
}
//静态方法,有参数,有返回值,习惯用大驼峰
public static int MyMethod2(int a, int b)
{
return a + b;
}
//1.6 事件---------------------------------------------------------
//详见《函数方法-含委托事件》章节
}
//2、类的成员的调用==================================================================
puclic class Program
{
static void Main()
{
MyClass mc = new MyClass();
mc.MYCONST1; //读取实例常量
MyClass.MYCONST1; //实例常量也可以通过类读取,具有静态特征
MyClass.MYCONST2; //读取静态常量
//mc._myField1; //报错,私有字段无法读取
mc.myField2 = "mc"; //设置公有的实例字段值
MyClass.myField3; //读取公有的静态字段值
mc.MyProperty1; //读取公有的实例属性
mc.MyProperty1 = "mc"; //设置公有的实例属性
//mc.MyProperty4 = 20; //报错,只读属性不能修改
//mc.MyProperty5 = 20; //报错,只能在类的内部修改
//mc.MyProperty6 = 20; //报错,只能在类的初始化时(即构造函数内)修改
MyClass.MyProperty7; //读取公有的静态属性值
mc.MyMethod1(); //调用公有的实例方法
int result = MyClass.MyMethod2(1,2); //调用公有的静态方法
}
}
2.2 TS的属性和方法
2.2.1 类的可见修饰词:类通常是公开可见的,不用可见修饰符的
2.2.2 类的成员的可见修饰词
- public: 可以被任何代码访问,默认值
- protect:只能在类的内部和派生类中被访大厅地硒鼓问
- private: 只能在类的内部被访问
2.2.3 类的实例成员和静态成员
- 实例成员:类实例化后,才能访问和修改,数据保存在实例化后的对象中,正常创建的都是实例成员
- 静态成员:直接通过类名访问和修改,数据保存在类型元数据中,使用static修饰。实际上类的实例也可以访问静态成员,静态成员是被所有实例共享的
2.2.4 类的成员代码示例
*特别说明:C#和TS中,都有属性的概念,但C#中属性本质是方法,是对字段的封装;TS中,属性就是C#中的字段,可以通过getter和setter函数,实现C#中的属性
//1、类的成员定义====================================================================
class MyClass {
//1.1 常量属性(运行时)-----------------------------------------
//没有编译时常量属性,只有readonly运行时常量属性,下节构造函数再说明
/*关于const和readonly总结:
C#:const和readonly均可在类中定义常量字段,const可以在方法和块级作用域中定义常量
TS:const在全局、方法和块级作用域中定义常量,readonly在类中定义运行时常量属性
const定义的常量字段是编译时,必须在定义时赋值
readonly定义的常量字段/属性是运行时,在构造函数中赋值
*/
//1.2 属性---------------------------------------------
private myProperty1:string = "mc"; //私有实例属性,并初始化,小驼峰
myProperty2:number; //默认公有
public static myProperty3:string = "function"; //静态属性
//1.3 getter和setter-------------------------------------
private _myProperty4:number;
get myProperty4(){ return this._myProperty4; }
set myProperty4(newValue:number){ this._myProperty4 = newValue; }
//1.4 方法---------------------------------------------
//实例方法,小驼峰
myMethod1(): void {
console.log('普通方法被调用');
}
//静态方法
static myMethod2(): void {
console.log('静态方法被调用');
}
}
//2、类的成员的调用==================================================================
let mc = new MyClass();
//mc.myProperty1; //报错。私有属性不能在类的外面读取
//mc.myProperty1 = 'function'; //报错。私有属性不能在类的外面修改
mc.myProperty2 = 10; //属性赋值
MyClass.myProperty3; //读取公有的静态属性
mc.myProperty4; //读取getter属性
mc.myProperty4 = 20; //修改setter属性
mc.myMethod1(); //调用实例方法
MyClass.myMethod2(); //调用静态方法
三、构造函数
3.1 C#中的构造函数
//1、构造函数的定义和重载============================================================
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
//构造函数,名称和类名一致,没有返回值及其类型修饰词
//在构造函数中完成属性初始化
public Person(string name, int age){
Name = name; //也可以this.Name = name。如果字段和参数同名,则必须使用this
Age = age;
}
//可以定义多个构造函数,实现构造函数的重载
public Person(){
Console.WriteLine("无参构造函数")
}
//*注意:当没有定义构造函数时,会有一个默认的无参构造函数
//*但是一旦定义任何构造函数,则不会有默认的无参构造函数
}
public class Program
{
public static void Main()
{
//调用无参构造函数。如果Person类中没有定义无参构造函数,会报错
//因为一旦显式定义了构造函数,就不会有默认的无参构造函数,如要使用,需显式定义
var p1 = new Person();
//调用有参构造函数
var p2 = new Person("mc",28);
//使用初始化构造器,为属性赋值
//本质是对象赋值的语法糖,相当于p3.Name = "MC";P3.Age = 28;
var p3 = new Person(){ Name = "mc", Age = 28 };
}
}
//2、在构造函数中调用父类构造函数=====================================================
//继承自父类的字段或属性,需要在构造函数中调用父类构造函数来初始化
public Student : Parent{
public string Grade { get; set; }
//通过在子类构造函数中,:base(参数),调用父类构造函数,并传入参数,初始化继承的属性
public Student(string name, int age, string grade): base(name,age)
{
Grade = grade
}
}
//3、readonly运行时常量字段的使用====================================================
public class Program
{
static void Main(string[] args)
{
var s1 = new Shape(1.0,2.0);
//s1.NumberOfSides = 5; //报错,readonly常量无法修改
}
}
public class Shape
{
//readonly常量,通常定义为实例字段,是运行时常量,声明时不初始化
public readonly int NumberOfSides;
//readonly常量,也可以定义为静态字段,声明时初始化
public static readonly string Color = "red";
//readonly常量NumberOfSides的值,在运行时决定,可以根据不同的构造函数设置不同的值
public Shape(double side1,double side2)
{
this.NumberOfSides = 4;
}
public Shape(double side1,double side2,double side3)
{
this.NumberOfSides = 3;
}
public void SetNumberOfSides()
{
//NumberOfSides = 5; //报错,类的普通函数中无法修改readonly常量
}
}
3.2 TS中的构造函数
//1、构造函数的定义和重载============================================================
class Person {
public name: string;
public age: number;
//构造函数的方法名为constructor,没有可见修饰词
constructor(name: string, age: number) {
this.name = name; //必须使用this,this是类实例化后的那个对象
this.age = age;
}
//无参构造函数,方法重载
constructor() {
console.log('无参构造函数');
}
//和C#一样,如果类没有定义任何构造函数,也会有一个默认的无参构造函数
//和C#一样,如果类定义了任何构造函数,则不会有这个默认的无参构造函数
}
let p1 = new Person(); //调用无参构造函数来创建实例
let p2 = new Person('mc', 28); //调用有参构造函数来创建实现
//TS中没有初始化构造器
//2、在构造函数中调用父类构造函数=====================================================
class Student extends Parent {
public grade: string;
//在子类构造函数中,通过super()调用父类构造函数
constructor(name: string, age: number, grade: string) {
super(name, age);
this.grade = grade;
}
}
//3、在构造函数中定义属性的简写方式==================================================
//构造函数中的属性访问修饰符,如果不加,默认是私有的,需要注意
class MyClass {
constructor(public name: string, public age: number) {}
}
const mc = new MyClass('Alice', 25);
//4、readonly运行时常量字段的使用====================================================
class Shape
{
public static readonly Color:string = "red";
public readonly NumberOfSides:number;
constructor(side1:number,side2:number){
this.NumberOfSides = 4;
console.log("构造一个矩形");
}
public setNumberOfSide():void{
//this.NumberOfSides = 5; //报错,类的普通函数中无法修改readonly常量
}
}
var s1 = new Shape(1.0,2.0);
//s1.NumberOfSides = 5; //报错,实例常量无法修改
四、抽象类
4.1 C#抽象类
- 抽象类不能被直接实例化。
- 可以包含抽象方法和非抽象方法。
- 抽象方法只有声明,没有具体实现,必须在派生类中实现。
- 派生类必须实现抽象类中的所有抽象方法。
- 常用于定义一些具有共同特性,但部分行为不明确的类层次结构。
abstract class Animal
{
public abstract void MakeSound(); //抽象方法
public void Move(){Console.WriteLine("动物在移动");} //实例方法
}
class Dog : Animal
{
public override void MakeSound() //必须实现父类的所有抽象方法
{
Console.WriteLine("汪汪");
}
}
class Program
{
static void Main()
{
Dog dog = new Dog();
dog.MakeSound(); //实现的抽象方法
dog.Move(); //继承的实例方法
}
}
4.2 TS抽象类
- 和C#没有区别
abstract class Animal {
abstract makeSound(): void;
move() {
console.log('动物在移动');
}
}
class Dog extends Animal {
makeSound() {
console.log('汪汪');
}
}
const dog = new Dog();
dog.makeSound();
dog.move();
五、静态类-仅C#
- 静态类不能被实例化,不能被继承,也不能继承其它类
- 静态类中的成员必须都是静态的,比如静态方法、静态字段、静态属性等
- 常用于定义一些工具类或提供全局可用的功能
- 静态类在程序运行期间一直存在,其成员可以直接通过类名访问,无需创建对象
class Program
{
static void Main()
{
StaticClass.DoSomething();
}
}
static class StaticClass
{
public static void DoSomething()
{
Console.WriteLine("执行静态类中的操作");
}
}
六、封闭类-仅C#
- 密封类不能被继承,除此之外和普通类没有区别,如可以定义任何类的成员,可以继承其它类或实现接口
- 通过在框架设计时会用到,实际业务应用中,比较少用到
sealed class SealedClass
{
public void DoSomething()
{
Console.WriteLine("执行密封类中的操作");
}
}
class Program
{
static void Main()
{
// 可以正常创建密封类的实例并使用
SealedClass sealedObj = new SealedClass();
sealedObj.DoSomething();
// 不能从密封类派生出新类,下面这样会报错
//class DerivedSealedClass : SealedClass { }
}
}
七、彩蛋
7.1 TS客户端开发中,较少使用class的原因
- 我看别人都是type或interface,所以就跟着type或interface了
- type或interface,都专属于typescript,编译时进行类型检验,转义为javascript后,type或interface的代码都不存在,而class是属于javascript的内容,是实实在在存在原码中的。在运行时,没有type或interface,理论上性能会好一丢丢。