C#基本语法整理-面向对象编程的额外技巧

244 阅读9分钟

一.引言

C#作为一个面向对象的语言,和Java差异不大,所以这里就不做赘述了,只是想整理一下C#中特别的语法

二.不同于其他面向对象语言的技巧收纳

  1. 可以声明一个和当前类同名的类对象,但是不能初始化,因为调用默认构造函数,会初始化属性,如果对当前同类型的属性进行实例化,会再次进入构造函数,直接内存溢出
           class Person
        {
            //女朋友
            //如果要在类中申明一个和自己相同类型的成员变量时
            //不能对它进行实例化
            public Person gridFriend; // 可以赋值为null
            //朋友
            public Person[] boyFriend;
        }
    
  2. 可以通过default(类型)查看该类型的默认值,比如int默认值是0
  3. 关于构造函数的重载,C#和Kotlin这块类似,可以调用this的构造函数
    class Person
    {
        public string name;
        public int age;
    
        public Person(int age)
        {
            //this代表当前调用该函数的对象自己 
            this.age = age;
        }
    
        public Person(string name)
        {
            this.name = name;
        }
    
        public Person(int age, string name) : this(age + 10)
        {
            Console.WriteLine("Person两个参数构造函数调用");
        }
    }
    
    需要记住的一点是,会优先调用this()构造函数,然后才回过来调用new对象时使用的构造函数,Kotlin也是如此
  4. 析构函数和垃圾回收
    • 析构函数
      这个只会在C#中使用到,Unity中不会使用,仅做了解
      析构函数是在引用类型的堆内存被回收时调用
      // 基本语法如下
      // ~类名()
      // {
      // }
      class Person
      {
          //当引用类型的堆内存被回收时
          //析构函数 是当垃圾 真正被回收的时候 才会调用的函数
          ~Person()
          {
      
          }
      }
      
    • 垃圾回收
      垃圾回收在Java阶段已经充分了解过了,这里就不赘述,明白年轻代的复制清除法以及分代模型即可
          //手动触发垃圾回收的方法 
          //一般情况下 我们不会频繁调用
          //都是在 Loading过场景时 才调用
          // 避免游戏过程中发生STW现象
          GC.Collect();
      
  5. 成员属性
    成员属性是C#中比较特别的,有点类似于Java中的Get/Set方法
    // 基本语法
    // 访问修饰符 属性类型 属性名
    // {
    //     get{}
    //     set{}
    // }
        class Person
        {
        private string name;
    
        //属性的命名一般使用 帕斯卡命名法
        public string Name
        {
            get
            {
                //可以在返回之前添加一些逻辑规则 
                //意味着 这个属性可以获取的内容
                return name;
            }
            set  // get set 还可以加权限修饰符,但范围要小于外层的修饰符
            {
                //可以在设置之前添加一些逻辑规则 
                // value 关键字 用于表示 外部传入的值
                name = value;  // 这里的value类似于Kotlin中的field
            }
        }
    
    简单说,C#中的成员属性就是Java中的set/get方法的浓缩,将其与成员变量结合在一起,更简洁一点,很多时候也会直接声明成员属性而非成员变量
    成员属性代码链接
  6. 索引器
    类似于成员属性和方法的结合体,针对类中有数组的情况,其实没有数组也可以很方便的获取到类对象中的一些状态值,所以针对不同的状态获取也是挺便捷的
    功能:让对象可以像数组一样通过索引访问其中元素,使程序看起来更直观,更容易编写
    • 语法
      //访问修饰符 返回值 this[参数类型 参数名, 参数类型 参数名.....]
      //{
      //      内部的写法和规则和索引器相同
      //      get{}
      //      set{}
      //}
      
    • 示例
      class Person
      {
          private string name;
          private int age;
          private Person[] friends;
      
          private int[,] array;
      
          #region 知识点五 索引器可以重载
          //重载的概念是——函数名相同 参数类型、数量、顺序不同
          public int this[int i, int j]
          {
              get
              {
                  return array[i, j];
              }
              set
              {
                  array[i, j] = value;
              }
          }
      
          public string this[string str]
          {
              get
              {
                  switch (str)
                  {
                      case "name":
                          return this.name;
                      case "age":
                          return age.ToString();
                  }
                  return "";
              }
          }
          #endregion
      
              public Person this[int index]
              {
      
              get
              {
                  //可以写逻辑的 根据需求来处理这里面的内容
                  #region 知识点四 索引器中可以写逻辑
                  if (friends == null ||
                      friends.Length - 1 < index)
                  {
                      return null;
                  }
                  #endregion
                  return friends[index];
              }
              set
              {
                  //value代表传入的值
                  //可以写逻辑的 根据需求来处理这里面的内容
                  if (friends == null)
                  {
                      friends = new Person[] { value };
                  }
                  else if (index > friends.Length - 1)
                  {
                      //自己定了一个规则 如果索引越界 就默认把最后一个朋友顶掉
                      friends[friends.Length - 1] = value;
                  }
                  friends[index] = value;
              }
            }
      }
      
  7. 静态类
    使用static修饰的类,类似于Kotlin中用object修饰的类,类中只能包含静态的成分
    静态构造函数
    • 静态类和普通类都可以有
    • 不能使用访问修饰符
    • 不能有参数
    • 只会自动调用一次(只要调用静态类中的任何一个成分时,就会调用,主要用于初始化成员变量,非静态的类同理) 静态类与静态构造函数代码链接
  8. 拓展方法
    和Kotlin中的扩展方法很像,唯一不同的就是写法上的差异
    • 语法
      访问修饰符 static 返回值 函数名(this 拓展类名 参数名, 参数类型 参数名,参数类型 参数名....)
      
    • 示例
      在C#中是通过一个静态类来包裹我们要扩展的方法,而Kotlin是在一个File中
      static class Tools
      {
          //为int拓展了一个成员方法
          //成员方法 是需要 实例化对象后 才 能使用的
          //value 代表 使用该方法的 实例化对象
          public static void SpeakValue(this int value)
          {
              //拓展的方法 的逻辑
              Console.WriteLine("唐老狮为int拓展的方法" + value);
          }
      
          public static void SpeakStringInfo(this string str, string str2, string str3)
          {
              Console.WriteLine("为string拓展的方法");
              Console.WriteLine("调用方法的对象" + str);
              Console.WriteLine("传的参数" + str2 + str3);
          }
          // 为自定义的一个类定义的拓展方法
          public static void Fun3(this Test t)
          {
              Console.WriteLine("为test拓展的方法");
          }
      }
      
      用法和Kotlin中的扩展方法一样
      注意:如果我们拓展的方法名和原来类中的成员方法重名后,会优先调用原本的成员方法,是覆盖不了的
  9. 运算符重载
    用得比较少,这里就做一个了解吧
    • 语法
      public static 返回类型 operator 运算符(参数列表)
      
    • 示例
          class Point
          {
              public int x;
          public int y;
      
          public static Point operator +(Point p1, Point p2)
          {
              Point p = new Point();
              p.x = p1.x + p2.x;
              p.y = p1.y + p2.y;
              return p;
          }
      
          public static Point operator +(Point p1, int value)
          {
              Point p = new Point();
              p.x = p1.x + value;
              p.y = p1.y + value;
              return p;
          }
      
          public static Point operator +(int value, Point p1)
          {
              Point p = new Point();
              p.x = p1.x + value;
              p.y = p1.y + value;
              return p;
          }
          // 使用
          Point p = new Point();
          p.x = 1;
          p.y = 1;
          Point p2 = new Point();
          p2.x = 2;
          p2.y = 2;
      
          Point p3 = p + p2;
      
      关于哪些运算符可以重载,哪些不可,详情见代码链接
      运算符重载代码链接
  10. 内部类和分部类
    内部类已经司空见惯,这里主要是积累分部类
    • 分部类概念
      简单说就是把一个类分成多个部分来写,也就是说同一个文件中可以存在很多个同名的类,不过得用partial来声明它是一个分部类,并且分布类中不能有重复的成员变量
    • 示例
          partial class Student
          {
              public bool sex;
              public string name;
      
              partial void Speak();
          }
      
          partial class Student
          {
              public int number;
      
              partial void Speak()
              {
                  //实现逻辑
              }
      
              public void Speak(string str)
              {
      
              }
          }
      
      仅仅是做一个了解,分部方法有点特别,有点类似于C语言中方法要在头部声明一样,将声明和实现分离
      详情看代码链接即可分部类和分部方法链接
  11. 里氏替换原则
    从接口的角度来说就是面向接口编程,用父类承载子类,这里的示例语法和Kotlin很类似
    任何父类出现的地方,子类都可以替代
    class GameObject
    {
    
    }
    
    class Player : GameObject
    {
        public void PlayerAtk()
        {
            Console.WriteLine("玩家攻击");
        }
    }
    
    class Monster : GameObject
    {
        public void MonsterAtk()
        {
            Console.WriteLine("怪物攻击");
        }
    }
    
    class Boss : GameObject
    {
        public void BossAtk()
        {
            Console.WriteLine("Boss攻击");
        }
    }
    #endregion
    
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("里氏替换原则");
    
            //里氏替换原则 用父类容器 装载子类对象
            GameObject player = new Player();
            GameObject monster = new Monster();
            GameObject boss = new Boss();
    
            GameObject[] objects = new GameObject[] { new Player(), new Monster(), new Boss() };
    
            #region 知识点三 is和as
            //基本概念 
            // is:判断一个对象是否是指定类对象
            // 返回值:bool  是为真 不是为假
    
            // as:将一个对象转换为指定类对象
            // 返回值:指定类型对象
            // 成功返回指定类型对象,失败返回null
    
            //基本语法
            // 类对象 is 类名   该语句块 会有一个bool返回值 true和false
            // 类对象 as 类名   该语句块 会有一个对象返回值 对象和null
            for (int i = 0; i < objects.Length; i++)
            {
                if (objects[i] is Player)
                {
                    (objects[i] as Player).PlayerAtk();
                }
                else if (objects[i] is Monster)
                {
                    (objects[i] as Monster).MonsterAtk();
                }
                else if (objects[i] is Boss)
                {
                    (objects[i] as Boss).BossAtk();
                }
            }
    
            #endregion
        }
    }
    
    在接口层用的更多一点,或者抽象类
  12. 继承中的构造函数
    大部分和Kotlin的语法结构很类似,需要注意如下几点
    • 使用base指代父类,而非super,继承的构造方法的重载写法和Kotlin一样
    • 对构造函数进行重载时,会优先执行冒号后面的this构造函数 继承中构造函数的代码链接
  13. 基本类型和引用类型的拆装箱
    在Kotlin中基本类型是对象类型,但是如果不是可空的话,会自动使用基本的类型,C#中也可以将基本类型用object来承载,称之为装箱,强转为对应的基本类型称之为拆箱
    注意:装箱会将栈内存会迁移到堆内存中,拆箱会将堆内存会迁移到栈内存中(拆装箱尽量少用,存在内存消耗)
    C#类型拆装箱代码链接
  14. 密封类
    Kotlin中也有,含义和功能都是一样
    使用关键字sealed声明
    主要作用就是不允许最底层子类被继承,可以保证程序的规范性、安全性
    密封类代码链接
  15. 密封方法
    关键字和密封类一样,用密封关键字sealed 修饰的重写函数,让虚方法或者抽象方法之后不能再被重写,特点是和override一起出现
    密封方法代码链接
  16. 抽象类和多态
    这里简称v(virtual)o(override)b(base)
    这里需要说明一个点
    • 在C#中不仅有abstract抽象方法:具体行为完全由子类决定
    • 还有virtual虚方法:子类可以重写进行覆盖,这也是多态的关键点
    • 还有没有任何关键字声明的普通方法:父类中的普通方法只能被孩子继承使用无法重写 多态代码链接
  17. 对命名空间namespace的理解
    • 类似于Android中不同包与包之间的关系,如果想使用必须导包,或者导入命名空间
    • 一个解决方案中可以有多个项目,项目与项目之间的关系就类似于Android中模块与模块的关系,相互独立,但是又可以进行联系
  18. 万类之父-Object
    C#中有属于自己的特色的Object的成员方法和静态方法,详情看代码链接
    万类之父Object相关代码链接