第壹章第8节 C#和TS语言对比-类

46 阅读11分钟

类的用法,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,理论上性能会好一丢丢。