Unity中常用的C#必知必会
  EFTJ6596AiAP 2023年11月02日 89 0

日期

修订内容

20221118

补充C#编译过程;补充各种数据容器的使用案例

20221119

补充类部分关于静态类、抽象类、密封类的描述


Unity & C#

.Net

.net是微软提供的平台,开源、(最初是不支持的)支持跨平台(Linux和macOS)、支持多种语言使用。

.Net提供源代码库,包含基类库(BCL)(被认为是一个庞大的OOP代码库),促进了其所支持的编程语言之间的互操作性。.Net也包括公共语言运行库(CLR),复杂执行.net库开发的所有运行程序。

.net framework后来被多次分叉,每个分叉都会修改自己的BCL。.net standard提供可以跨分支的应用程序逻辑,.net core是.net库的开源、跨平台版本。到2020年,.net有了一个新版本就叫.net,开源、跨平台、不必支持多个版本和分支。

所以最初的.net是不跨平台的,需要配合.net standard、.net core来使用,但是2020年之后就不用再提这两个分支了。

用.net编程就意味着用库中的现有代码来编程。


C#

编译过程

Unity早期支持Boo、UnityScript(无限接近JavaScript)、C#,其中Boo是unity自己开发的,比较冷门,UnityScript则是面向过程的,不易维护,所以我们需要使用面向对象的C#。C#是包含在.Net中的一种语言,是C++的一种演变。

在Unity中C#语言经过以下的过程被编译成二进制机器码。

Unity中常用的C#必知必会_c#

主要是分为两个过程:第一步,将C#编译成中间语言(CIL),这个编译步骤由Visual Studio完成;第二步,将中间语言通过CLR编译成原生机器码。

CIL是独立于计算机、操作系统和CPU的。

CLR编译会使用合适的编译器来创建所需的本机代码。

语言分类

维度1:按编译过程

  • 编译型:预先就编译成机器语言了;优势:速度快;like C/C++
  • 解释型:运行的时候才进行编译;优势:跨平台性好,开发效率高;like java/c#

维度2:按抽象程度

  • 机器码
  • 汇编
  • 高级语言
  • 脚本语言

维度3:按类型是否安全

  • 强类型: 类型安全
  • 弱类型

总结:C#是一种解释型、类型安全的高级编程语言。


Unity的跨平台解决方案

跨平台意味着在一个平台上开发的软件或者程序可以直接在其他平台上正常运行,而不需要对原始文件和代码进行修改。

微软的C#编程语言运行在.NetFramework上,只能运行在Windows上,无法跨平台,所以需要借助跨平台解决方案。


这些概念的关系:C#、.net、Mono、IL、CLR、JIT、IL2CPP、dll

  • .net:是一个开发框架,其中包含编译器、虚拟机CLR、一个广泛的类库BCL。​​有三个版本.net​​ Framework、.net Core、Xamarin。
  • C#:运行于.net上的高级程序设计语言,是一种编译语言,不是脚本语言。C#用于生成面向.net环境代码,但是不是.net的一部分。
  • Mono:不是微软推出的,包含C#编译器和通用语言架构,为C#提供集成开发环境,集成并且实现了.net编译器、CLR、基础类库,解决了C#的跨平台问题。
  • IL:Intermediate Language,.net框架的中间语言,可以被.net编译器直接编译为.exe和.dll文件,但是此时的代码不是CPU能直接执行的机器码,而是一种IL。
  • CIL:Common Intermediate Language,特指在.Net平台下的IL标准。
  • CLR:Common Language Runtime,公共语言运行时,制定了一套完整的规范,描述了一个程序完整生命周期中所需要的所有细节。
  • JIT:Just InTime,Mono中用来将IL转换成汇编代码的技术。
  • DLL:Dynamic Link Library, 动态链接库。编译应用程序的时候,所创建的CIL代码都是存储在一个程序集中,程序集中包含:exe(可以直接windows上运行,不需要其他程序的) + dll(其他应用程序使用的库)。Windows操作系统中的大部分功能都是由DLL提供的。
  • IL2CPP:将IL变为C++,之后由IL2CPP VM提供GC管理、线程创建的工作。


.Net Core

.net core是.net库的开源、跨平台版本。到2020年,.net有了一个新版本就叫.net,开源、跨平台、不必支持多个版本和分支。


Mono

.net是一个语言平台,mono为.net提供集成开发环境,继承并实现了.net编译器、CLR、基础类库,用来解决C#的跨平台问题。

Mono是虚拟机,作用是解释执行IL,通过JIT技术转化成汇编代码,最后在目标平台执行程序,借此实现跨平台。

后来iOS禁止了JIT技术,因为JIT技术需要底层系统支持动态代码生成,对操作系统来说意味着需要支持动态分配带有可写可执行权限的内存,比较不安全。

Unity中常用的C#必知必会_解决方案_02


IL2CPP

这是iOS禁用JIT之后出现的解决方案,性能比Mono要快得多。

IL2CPP是一个编译工具,获得C#的IL中间码之后,通过IL2CPP技术转换成C++代码,然后各个平台的C++编译器将其直接编译成能执行的原生汇编代码。

Unity中常用的C#必知必会_c#_03

参考链接:​​Unity将来时:IL2CPP是什么? - 知乎 (zhihu.com)​


变量和表达式

表达式

流程控制

循环的中断

  • break:终止循环
  • continue:跳出本轮循环
  • return:跳出循环以及包含这个循环的函数

For、Foreach、Enumerator.MoveNext


遍历方式

内存

for

通过索引依次进行遍历


foreach

迭代

Update中不推荐使用foreach,因为会遗留内存垃圾

Enumerator.MoveNext

迭代



运算符

i++:先对左侧的变量进行赋值

++i: 先自增,再对变量进行赋值

i++不可以作为左值

代码:

int i = 11;

void Start()
{
Debug.Log(i++);
Debug.Log(i);
Debug.Log(++i);
}

输出效果:

Unity中常用的C#必知必会_解决方案_04

关键字

  • unsafe:非托管代码,用在带指针操作的场合;常用在背包系统的任务装备栏
  • ref:修饰引用参数,必须赋值,带回返回值;
  • out:修饰输出参数,可以不赋值,带回返回值之前必须明确赋值

这两个参数都不会创建新的存储位置


数据结构

数据类型

基础数据类型

这部分的笔记参见我之前的一篇整理:​​https://blog.51cto.com/u_15639010/5618062​

下面重点包含一些面试或者项目中需要经常用到的知识点,可能比较零散,但是可以帮助我们巩固重点知识,一步一步来吧。

简单的数据类型:有固定的取值范围


String

string & stringBuilder

函数中如果多次使用string+=,会产生大量的内存垃圾

可以使用StringBuilder的Append,可以减少内存垃圾,当需要字符串拼接的时候推荐stringbuilder


String

StringBuilder

所在的BCL

System.String

System.Text.StringBuilder

定义

字符串常量

字符串变量

性质

运算的时候是重新生成了新的string对象的

一直是在已有的对象进行操作的,已有对象可以改变

【20221118补充】StringBuilder的用法

(1) 定义和初始化

不可以直接将string类型的变量赋值给String

Unity中常用的C#必知必会_c#_05

(2)增删改查

代码:

public class BasicVariables : MonoBehaviour
{
string a = "abc";
string b = "def";

public void Start()
{
string c = a + b;
Debug.Log(c);

StringBuilder d = new StringBuilder("abc");
Debug.Log(d.Append(b));//字符串拼接
Debug.Log(d.Insert(2, "+++"));//字符串插入
Debug.Log(d.Remove(0, 2));//字符串删除
Debug.Log(d.Replace("+", "-"));//字符串替换
}
}

输出效果:

Unity中常用的C#必知必会_c#_06

复杂的变量类型

枚举

(1)枚举 & 结构体 & 数组


枚举

结构体

数组

功能

自己定义类型、类型的取值范围

自己定义变量类型;使得单一的变量可以存储多种数据类型的数据

访问相同类型的多个值,是一个有限集合

成员数量

1个值

1个值

多个相同类型的值

成员类型

值类型,默认可以int、string转换

值类型

引用类型

成员存储

离散的


有序、线性连续的内存空间


(2)枚举的类型转换

枚举使用一个基本类型来存储,默认的基本类型是int。

1)类型转换分类

  • 隐式转换:有条件的
  • 显式转换:可能会造成数据丢失;可以使用checked/unchecked来控制是否进行强制的类型检测
  • 使用convert进行转换:默认就是会进行类型检测的
  • 使用Parse进行转换

convert & parse


显式转换

convert

parse

写法

int

Convert.ToInt32()

int.Parse()

对null的处理方式


不会产生异常

会产生异常

对浮点数的处理方式

直接舍弃后面的小数


返回临近的偶数

3.5 => 4;4.5=>4

报错

转换类型

多种

多种

将string转换成num

2)枚举的类型转换

案例1:

两种声明枚举的方式:

//声明枚举方式1
public enum emAction { None, GetUp, Wash, Eat, Play, }//声明一个枚举

//声明枚举方式2
public enum emAction { None=0, GetUp, Wash, Eat, Play, }//后续的枚举值的数字编号依次递增,这里不一定是从0开始

枚举的类型转换:

void Func1()
{
//enum ——> string
mAction.ToString();
//string ——> enum,将字符串转换成枚举
mAction = (emAction)Enum.Parse(typeof(emAction), "Wash");//注意不能添加枚举中没有的值
Debug.Log("string——》enum:" + mAction);
mAction = (emAction)Enum.Parse(typeof(emAction), "Wash111");//会报错

//enum ——》int
int iPlay = (int)mAction;
int iConvertValue = Convert.ToInt32(mAction);

//int ——》 enum
mAction = (emAction)3;
mAction = (emAction)Enum.ToObject(typeof(emAction), 1);
Debug.Log(mAction);
}

案例2:

public enum Animal:int
{
cat,
dog,
mouse
}

public class BasicVariables : MonoBehaviour
{
public void Start()
{
Debug.Log((int)Animal.cat);//枚举——int
Debug.Log((Animal)2);//int——枚举
Debug.Log(Animal.dog.ToString());//枚举——字符串
Debug.Log((int)Enum.Parse(typeof(Animal), "cat"));//字符串——枚举
Debug.Log((Animal)Enum.Parse(typeof(Animal), "cat"));
}
}

输出效果:

Unity中常用的C#必知必会_c#_07

数据容器

各个数据容器的比较

两对很相似的对应关系:

HashTable和dictionary都不允许键重复


键值对

object

ArrayList

HashTable

泛型类

List<>

Dictionary<>

不同数据类型的特征比较:


顺序

长度

成员类型

描述

Array




顺序







长度固定




取决于成员的类型


ArrayList




顺序







长度不固定







Object,类型不安全




劣势:需要装箱和拆箱,所以速度慢、类型不安全;

优势:跟array相比,长度可变;可存放多种类型数据

List




顺序







长度不固定







泛型类,类型安全




跟ArrayList相比效率更高、类型安全

Dictionary




非顺序







长度不固定







泛型类,类型安全




键不允许重复

HashTable




非顺序







长度不固定







Object,类型不安全




键不允许重复

HashSet




非顺序







长度不固定







泛型类,类型安全




值不允许重复;不允许下标索引;无序;可进行交并补差

LinkedListNode




非顺序







长度不固定







泛型类,类型安全




通过前后指示index进行访问

LinkedList




非顺序







长度不固定







泛型类,类型安全




不允许下标索引;每个节点都是linkedlistnode类型

Stack

线性




长度不固定







泛型类,类型安全




入栈动态扩容2倍

Queue

线性




长度不固定







泛型类,类型安全





(1)补充代码:Array的成员类型

public enum Animal:int
{
cat,
dog,
mouse
}
public class BasicVariables : MonoBehaviour
{
Animal[] animals = new Animal[3];//值类型
System.Object[] objects = new System.Object[3];//引用类型

public void Start()
{
for (int i = 0; i < animals.Length; i++)
{
animals[i] = (Animal)i;
Debug.LogFormat("第{0}个元素: {1}", i, animals[i]);
}

for (int i = 0; i < animals.Length; i++)
{
objects[i] = (Animal)i;
Debug.LogFormat("第{0}个元素: {1}", i, objects[i]);
}
}
}

输出效果:

Unity中常用的C#必知必会_解决方案_08

(2)补充代码:单链表和双向链表

LinkedList<int> linkedlist = new LinkedList<int>();//双向循环链表

void Start()
{
//链表
linkedlist.AddLast(1);
linkedlist.AddFirst(345);
var num = linkedlist.Find(1);
linkedlist.AddAfter(num,2);
Debug.Log(linkedlist.Count);

LinkedListNode<int> current = linkedlist.First;
LinkedListNode<int> previous = null;

previous = current;
current = current.Next;
Debug.Log(previous.Value);
Debug.Log(current.Value);
}

输出效果:

Unity中常用的C#必知必会_解决方案_09

性能比较

  • 插入:LinkedList > Dictionary>HashTable>List
  • 删除:Dictionary > LinkedList >HashTable > List
  • 遍历:List > LinkedList > Dictionary > HashTable


各数据容器的操作

List删除和插入都需要后面的元素挪动位置,但是遍历是很方便的


添加元素

删除元素

查找元素

排序

获取长度

Array

通过下标进行赋值;如果想增加位数,需要新建数组

如果需要删除某一个元素,需要新建一个位数-1的数组,遍历赋值

通过下标

Array.Sort(array)

array.Length()


ArrayList

arrayList.Add(int n)

arrayList.AddRange(Array a)

arrayList.Remove(int n)

通过下标

arrayList.Sort()

如果成员有非数据类型的,会报错

arrayList.Count()


List

list.Add(a);

list.Remove(d);

Debug.Log(list[0]);

list.Sort()

Debug.Log(list.Count)

Dictionary

dictionary.Add(7, "seven")

dictionary.Remove(7)

dictionary[7] = "first one"

dictionary.Count

HashTable

 hashtable.Add("abc", "the different one")

hashtable.Remove(0)

 hashtable["abc"] = "second hashtable"

hashtable.Keys.Count

HashSet

hashSet.Add("123")

hashSet.Remove("123")

foreach(var hash in hashSet)

{Debug.Log(hash); }

无,本身就是无序的


hashSet.Count

LinkedListNode





LinkedList

 linkedlist.AddLast(1);

 linkedlist.AddFirst(345);

linkedlist.Remove(num);

linkedlist.RemoveFirst();

通过linkedlistnode的前后指示

linkedlist.Count

Stack

入栈stack.Push(d)

出栈stack.Pop()

stack.Peek()

stack.Count

Queue

入队queue.Enqueue(b)

出队queue.Dequeue()

queue.Peek()

queue.Count


数据分类

值类型 & 引用类型


父类

内涵

外延

内存情况

值类型

System.Object——System.ValueType

赋值相当于创建了一个副本,克隆了一个变量

内置值类型(简单类型):整型、浮点型、布尔值、字符

自己定义的值类型:枚举和结构体

  • 存取快
  • 变量直接存放实际的数据
  • 只需要一段单独的内存,在声明的时候进行分配
  • 局部变量存放在栈中,类的成员变量存放在堆中

引用类型

System.Object

赋值相当于赋值了对象的引用

内置引用类型:dynamic、object、string

需要自己声明的引用类型:class, interface, delegate, record

  • 存取慢
  • 变量存放数据的地址
  • 需要两段内存,引用地址放在栈中,数据放在堆中
  • 声明的时候在栈中分配地址,实例化的时候在堆中开辟一部分空间存储类的实例


堆栈

  • 堆:Heap,C里面叫堆,C#中叫托管堆,由CLR进行管理,堆满了之后会自动清理其中的垃圾,所以我们不需要关心其中的内存释放问题。?(这部分待确认)
  • 栈:Stack,堆栈,简称为栈。

内存堆栈:存放在内存中的两个存储区, 堆区和栈区

  • 栈区存放函数的参数、局部变量、返回值等,由编译器自动释放
  • 堆区存放引用类型的对象,由CLR进行释放

栈的溢出:无限递归、无限循环、超大量局部变量的分配


栈/堆栈

堆/托管堆

数据结构

  • 先进后出
  • 线性
  • 先进先出
  • 非线性

存放内容

代码执行步骤,比如一个值类型变量的初始化、一个方法的声明、函数的参数值

对象、数据等

内存管理

物理:都存在RAM中

逻辑:一级缓存中,栈顶元素用完之后立马释放,由编译器进行管理

物理:都存在RAM中

逻辑:二级缓存中,需要GC清理,由CLR进行管理


GC

操作系统负责分配释放

需要程序员分配释放,或者程序结束的时候OS回收?

空间和读取速度

空间小

读取速度快

空间大

读取速度慢

Unity中常用的C#必知必会_解决方案_10


装箱 & 拆箱

  • 值类型——引用类型,会需要分配内存,叫做装箱,会产生GC
  • 引用类型——值类型,叫做拆箱

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Box : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
int a = 20;
object b = (object)a;//值类型转换成引用类型,这里产生装箱,发生GC(内存分配)

object c = a;
int d = (int)b;//这是拆箱
}

// Update is called once per frame
void Update()
{
}
}


Class

概念

面向对象编程的基础,是对现实生活中一类具有共同特征的事物的抽象。定义类之后还需要实例化,不然不可以开始使用。如果没有设定命名空间,默认放在同一个全局名称空间,不允许类名重复。

成员变量 & 成员函数

类中包含类的成员变量和成员函数,其中静态成员变量和成员函数不能通过类的实例来进行访问,只能通过类名进行访问。

构造函数 & 析构函数

如果不手写,编译器会自动添加,需要自定义的时候可以自己写。

  • 构造函数:new一个实例的时候会自动执行,如果不手动写,那么系统会自动创建,手动写了之后系统就不会创建。
  • 如果用public修饰,那么类的对象可以使用这个构造函数进行实例化。
  • 如果用private修饰,那么仅类的静态成员会用了,不可以再用这个构造函数来创建新的实例。
  • public和private修饰的构造函数不能同时存在。

Unity中常用的C#必知必会_c#_11

  • 析构函数:当前类对象被销毁的时候执行。

构造函数&静态构造函数&私有构造函数的比较


构造函数

静态构造函数

私有构造函数

概述

就是泛指实例构造函数

非实例构造函数

一种特殊的实例构造函数

共同点


(1)具有初始化的作用

(2)没有返回值,声明的时候不可以用void修饰符

(3)两者可以同时存在(但是仅限于无参数的构造函数),执行的时候静态构造函数在对象实例化之前调用


功能



通常存在于只包含静态成员的类中,使得其他类无法创建这个类的实例,也无法继承这个类

调用时机与次数

每次实例化的时候调用,可以执行多次

在第一个实例对象创建之前被调用,只能执行一次

或者在静态成员被调用之前调用


修饰符

可以有修饰符,也可以有参数

没有任何修饰符,只能加static;也没有参数


代码示例:静态构造函数和构造函数同时存在的时候

static DataContainers()
{
Debug.Log("静态构造函数");
}
public DataContainers(int number)
{
Debug.Log("public构造函数");
}

输出效果:带参数的构造函数没有被执行

Unity中常用的C#必知必会_解决方案_12

static DataContainers()
{
Debug.Log("静态构造函数");
}
public DataContainers()
{
Debug.Log("public构造函数-无参数");
}
public DataContainers(int number)
{
Debug.Log("public构造函数-有参数");
}

输出效果:开始运行的时候执行静态构造函数,然后执行两次构造函数;停止运行的时候再出现一次。这种顺序和代码中写的顺序没有关系,都是静态构造函数先执行的。

Unity中常用的C#必知必会_解决方案_13

作用域 & 访问修饰符

作用域:函数内生命的变量、类对象,在执行完本函数的时候会被自动销毁,存放在栈里;但是类内声明的变量、类对象,只有类被销毁的时候,管理的变量、类对象才会被销毁,存在堆里。

访问修饰符:private(同一个类中的对象可以访问) < internal/protected(同一个程序集的对象可以访问) < protected(当前类或者继承类) internal < public 

  • private:当前class或者struct内部访问
  • internal:同一程序集内可以访问,可以从同一编译的代码中访问
  • protected:同一class或者该class的派生类中可以访问
  • protected internal:同一程序集 || 另一程序集中的派生类中访问
  • private protected:类内部 || 派生类中
  • public:同一程序集或者其他程序集

Unity中常用的C#必知必会_解决方案_14

不同类型结构的可访问性:

  • 类、枚举、结构体、接口、委托:只能声明为internal或public;如果不特殊声明,默认就是internal的
  • 类、结构体、委托的成员:以上6种都可以;如果不特殊声明,默认就是private的
  • 接口:
  • 可声明为internal和public;如果不特殊声明,默认是internal的;
  • 接口的成员默认是public,接口成员可以声明包含任何访问修饰符
  • 枚举
  • 枚举的成员始终都是public的
  • 派生类和派生记录的可访问性不能高于其基类;成员的可访问性不能大于该成员类型的可访问性

一些其他维度的修饰符:

  • static:用来修饰静态类和静态类成员
  • sealed作用:类中:防止继承;方法中:防止派生类进行重写。


作用

允许实例化

内部成员

被继承

静态类



不允许


只允许存在一个静态构造函数,不能包含实例的构造函数

只包含静态成员


不允许


抽象类



不允许


支持构造函数

允许虚函数

如果函数是abstract的,那么声明的时候不包含函数体,必须由子类进行覆盖


可以


密封类

使得类不能被继承

/?

不能声明成抽象类

成员函数不得声明成sealed


不允许

但是可以继承别的类(抽象、普通)、接口



静态类(Static)

  • 功能:静态类无法被实例化;效果等同于:只包含静态成员和私有构造函数的类,但是静态类编译器可以进行检查。
  • 特点:
  • 成员:只包含静态成员
  • 构造函数:只能有静态构造函数,不允许有实例构造函数
  • 继承和派生:静态类必须从对象派生,无法被继承
  • 调用:
  • 在程序中首次引用类之前初始化其字段并调用其静态构造函数。 静态构造函数只调用一次,在程序所驻留的应用程序域的生存期内,静态类会保留在内存中。
  • 静态类的调用是采用了中间语言MSIL生成调用指令,而实例方法的调用是生成了callvirt指令。
  • 静态构造函数
  • 不能有访问修饰符,不能有参数,只能加static
  • 但是静态构造函数不一定只出现在静态类中
  • 静态成员
  • 访问方式:直接通过类名访问静态成员
  • 常见用途:静态字段的两个常见用途是保留已实例化的对象数的计数,或是存储必须在所有实例间共享的值。
  • 静态方法可以重载,不能替代
  • C#中不支持静态的局部变量


const & readonly

const:

  • 访问限制:在外部访问只能通过类名进行访问,无法通过类的实例进行访问
  • 初始化:只能在声明的时候进行初始化,其他地方不可以初始化
  • 值是否可变:不可变值

readonly:

  • 访问限制:可以通过类的实例来进行访问
  • 初始化:初始化可以在构造函数和声明的时候
  • 值是否可变:不可以修改值


抽象类(Abstract)

  • 功能:创建不完整且必须在派生类中实现的类和class成员。抽象类提供了可供多个派生类共享的通用基类定义,为子类提供设计思想,配合多态用于代码架构设计。
  • 特点:
  • 不能被实例化
  • 允许构造函数
  • 允许实例成员和实例方法
  • 抽象类的派生类必须实现所有的抽象方法

代码示例:

// compile with: -target:library
public class D
{
public virtual void DoWork(int i)
{
// Original implementation.
}
}

public abstract class E : D
{
public abstract override void DoWork(int i);
}

public class F : E
{
public override void DoWork(int i)
{
// New implementation.
}
}

说明:抽象类E将抽象类D中的虚方法声明为了一个abstract的抽象方法,这个新的抽象方法对于其子类来说依然是虚方法。这使得其子方法无法访问D中的方法,必须提供新的方法实现。

补充知识点:抽象方法和虚方法的区别


抽象方法

虚方法

关键词

abstract

virtual

使用

只能声明在抽象类中

不一定

表现

没有方法体,强制派生类实现

有方法体,子类可以覆盖也不可以覆盖

是否必须重写

必要

不必要

  • 如果一个类中定义了抽象类,那么是一定要在派生类中实现的;
  • 如果定义了虚方法,不一定要实现。


密封类(Sealed)

用于类:

  • 功能:
  • 提高密封类成员的调用效果
  • 如果用于包含虚函数的派生类,可以取消成员的虚效果
  • 特点:
  • 不可以被继承,不能用作基类,但是可以继承别的类
  • 不能声明成抽象类
  • 密封类的成员函数不得声明成sealed

用于成员:

  • 如果用于包含虚函数的派生类,可以取消成员的虚效果;
  • 表现:通常和override一起出现

public class D : C
{
public sealed override void DoWork() { }
}

错误案例:不是重写的虚方法,无法将其密封

Unity中常用的C#必知必会_c#_15


面向对象

概述:

  • 继承:将子类的公共属性集合起来设置为父类,以提高代码的重用度,增强可维护性,符合开闭原则;继承具有单根性,C#中一个类只能继承一个类。
  • 封装:通过约束代码的可访问性,增强数据的安全性;属性是C#封装的最好体现;将一系列复杂的逻辑包装起来,别人使用的时候不需要了解其中的逻辑,只需要传入参数就可以,降低了代码的耦合程度。
  • 多态:一个对象的多种状态,指同名方法在不同的环境下,反映出的不同表现。

封装

  • 意义:封装被称为是面向对象的第一支柱或者原则。
  • 含义:类或者结构可以指定自己的每个成员对于外部代码的可访问性,可以隐藏不希望外部访问的成员。将实现的细节封装成一个接口,调用的第三方不需要关心其细节。

用不同的方法实现父类中的方法:枚举;重载。

枚举案例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public enum EmPolygon { Rectangle, Triangle, }


public class Polygon//父类
{
public int iLength;
public int iWidth;
public string strColor;
public string strName;

public float fArea;

public EmPolygon emPolygon;//声明一个枚举

//这种思维就叫封装
public void ShowBaseInfo()
{
Debug.LogFormat("多边形长度:{0},宽度:{1},颜色:{2},名字:{3} ", iLength, iWidth, strColor, strName);

}


public void CalcArea()
{
/*
switch(emPolygon)
{
case EmPolygon.Rectangle:
fArea = iWidth * iLength;
break;
case EmPolygon.Triangle:
fArea = (iWidth * iLength) / 2;
Debug.Log("这是一个三角形");
break;
}*/

if (emPolygon == EmPolygon.Rectangle)
{
fArea = iWidth * iLength;

}
else if (emPolygon == EmPolygon.Triangle)
{
fArea = iWidth * iLength /2;
Debug.Log("这是一个三角形的面积");
}
}

}


public class Rectangle: Polygon//子类
{
public Rectangle()
{

}

~Rectangle()
{

}

}


public class Triangle : Polygon
{
public Triangle()
{

}

~Triangle()
{

}

}


public class Inheritance : MonoBehaviour
{
void Start()
{
Rectangle recTangle = new Rectangle();
recTangle.emPolygon = EmPolygon.Rectangle;//选择枚举中的一个类型
recTangle.iLength = 3;
recTangle.iWidth = 4;
recTangle.strName = "矩形";
recTangle.strColor = "红色";
recTangle.ShowBaseInfo();
recTangle.CalcArea();
Debug.Log(recTangle.fArea);

Triangle triangle = new Triangle();
triangle.emPolygon = EmPolygon.Triangle;
triangle.iLength = 6;
triangle.iWidth = 3;
triangle.strName = "三角形";
triangle.strColor = "Blue";
triangle.ShowBaseInfo();
triangle.CalcArea();
Debug.Log(triangle.fArea);
}
}

重写案例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public enum EmPolygon { Rectangle, Triangle, }


public class Polygon//父类
{
public int iLength;
public int iWidth;
public string strColor;
public string strName;

public float fArea;

public EmPolygon emPolygon;//声明一个枚举

//这种思维就叫封装
public void ShowBaseInfo()
{
Debug.LogFormat("多边形长度:{0},宽度:{1},颜色:{2},名字:{3} ", iLength, iWidth, strColor, strName);
}


public virtual void CalcArea()//虚函数
{
}
}


public class Rectangle: Polygon//子类
{
public Rectangle()
{

}

~Rectangle()
{

}

public override void CalcArea()
{
fArea = iWidth * iLength;
}

}


public class Triangle : Polygon //三角形子类
{
public Triangle()
{

}

~Triangle()
{

}

public override void CalcArea()
{
fArea = (iWidth * iLength) / 2;
}

}


public class Inheritance : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Rectangle recTangle = new Rectangle();
recTangle.emPolygon = EmPolygon.Rectangle;//选择枚举中的一个类型
recTangle.iLength = 3;
recTangle.iWidth = 4;
recTangle.strName = "矩形";
recTangle.strColor = "红色";
recTangle.ShowBaseInfo();
recTangle.CalcArea();
Debug.Log(recTangle.fArea);

Triangle triangle = new Triangle();
triangle.emPolygon = EmPolygon.Triangle;
triangle.iLength = 6;
triangle.iWidth = 3;
triangle.strName = "三角形";
triangle.strColor = "Blue";
triangle.ShowBaseInfo();
triangle.CalcArea();
Debug.Log(triangle.fArea);

}
}

继承

含义:派生自一个类的类自动包含基类的所有公共、受保护的和内部成员(不包含其构造函数和终结器)。

this & base

  • base的作用域:父类中找变量和方法
  • this的作用域:当前类中的变量和方法

知识点:重载和重写的区别


重载

重写

发生对象

同一个定义域(比如类)中

父子类之间

功能

一个参数名称方法的多种实现形式

实现多态

表现形式

名称相同、参数或者返回值不同

  • 名称相同、参数相同
  • 方法体不同
  • 通过virtual/abstract和override实现

多态

当派生类从基类继承的时候,他就会获得基类的所有方法、字段、属性和事件。派生类可以对其做出不同的设计。

  • 静态多态:是指在方法参数和集合或数组等位置,编译的时候,通过函数重载、运算符重载实现。
  • 动态多态:是指运行的时候实现,运行的时候根据不同的对象执行一个函数的不同行为,只有运行的时候才能确定具体执行什么样的行为。

重载 & 覆盖/重写

  • 覆盖/重写:发生于继承关系,通过virtual/abstract和override实现,函数名和参数都是一样的。
  • 重载:发生于任何关系,函数名一样,但是函数的参数不一致,就可以叫做重载。

参数是否一致的判断维度:数量、类型、顺序


泛型

含义:C#引入了类型参数

特点:

  • 声明的时候可以不指定具体类型
  • 可以指定泛型类型约束,T可以是常见的数据类型+自定义的类
  • 如果子类也是泛型的,那么继承的时候可以不指定具体类型

用处:处理一组功能一样,但是类型不同的任务

优势:可重用、类型安全、高效

泛型约束:可以创建自己的泛型接口、泛型类、泛型方法、泛型事件、泛型委托

  • class:T必须是一个类
  • struct:T必须是一个结构类型
  • new () :T必须要有一个无参数的构造函数
  • 基类的名字:T必须继承这个基类
  • 接口的名字:T必须实现这个接口

泛型类

如果父类是泛型,继承的写法有以下三种:

public class BaseParent<T, X>
{

}

//方式1:子类不指定类型
public class Son1<T, X>: BaseParent<T, X>
{

}

//方式2:子类直接指定类型
public class Son2: BaseParent<int, string>
{
public void Show<X>(X A)//泛型的方法不需要在类名中进行限定,只需要在调用的时候限定就可以
{
Debug.Log("A=" + A);
}
}

//方式3:子类增加别的类型
public class Son3<T, X, Y, Z>: BaseParent<T, X>
{

}

public class Son4<X, Y>: BaseParent<int, string>
{

}


接口

特点:

  • 只声明接口函数,不包含实现;也就意味着接口只提供派生类应该遵循的标准结构,接口成员函数的定义是派生类的责任
  • 访问修饰符必须是public
  • 不可以实例化
  • 可以继承其他接口
  • 可以继承多个接口

用处:用于约束一些行为

接口和抽象类的比较


接口

抽象类

变量


不允许



允许


构造函数


不允许



允许


函数实现


不允许



允许


允许实例函数、抽象函数、虚函数;其中实例函数和虚函数都是包含方法体的

访问修饰符

默认public,可以改为internal;

其成员默认是public,可以修改。


默认是internal的,允许public、internal。

其成员默认是private的,可以修改。

多重继承

允许

不允许,只能单根

相同点

实例化:都不允许实例化

函数:接口和抽象类中的抽象方法都是声明函数但是不含实现

派生类:都必须去实现接口或者抽象类的抽象方法。

接口和委托的比较


接口

委托

概念

约束类应该具备的功能集合

函数的容器,本质上是一个类

解决的问题

约束类,便于类的管理和拓展

解决类的单继承问题

约束函数,便于函数的管理和拓展

使用场景

多重继承

完全抽象

多用于事件处理中,约束事件处理器


结构体


结构体

变量

普通变量:全局变量不能再声明中直接赋值,只能在构造函数中赋值;

const、readonly:结构体中readonly只能在构造函数中赋值

普通变量:类在哪里都可以

构造函数


不允许定义无参构造函数,可以定义有参数的构造函数



允许


析构函数


不允许



允许


访问修饰符

函数不允许声明为virtual

类型不允许声明为abstract

类都可以

使用

访问变量:变量不需要实例化

访问函数:需要实例化

访问变量:需要实例化

访问函数:需要实例化

底层数据结构

值类型

引用类型

实例化

值类型,不会分配堆内存,仅仅调用结构体的构造函数初始化

引用类型,实例化的时候会在堆上开辟内存,然后调用构造函数进行初始化

继承

不能互相继承

除了密封类,都可以相互继承

相同点

静态构造函数:都支持

自定义函数:都支持

const:const的使用方法都一样,都需要在声明的时候就进行赋值


委托

概念

本质上是一个类型

//以下是声明委托的两种方式
delegate void MyDelegate1(int x);
delegate void MyDelegate2<T>(T x);

//以下是使用委托的两种方式
MyDelegate1 myDelegate = new MyDelegate1 (func);
MyDelegate1 myDelegate = func;

Todo:

  • UnityAction
  • Action
  • Event


JIT

AOT

含义

Just In Time 实时编译

Ahaed Of Time 预先编译

效率

执行慢

执行快

空间

占用空间小

占用空间大



一些还不知道怎么分类的知识点

反射的实现原理

  • 反射:审查元数据并收集关于它的类型信息的能力
  • 元数据:编译以后最基本的数据单元
  • 反射的作用:动态创建类型的实例,将类型绑定到现有对象;或从现有对象中获取类型。

取得Assembly的方法:
Assembly.Load 
Assembly.LoadFile 
Assembly.LoadFrom 
Type对象的Assembly方法

反射的成员:
MemberInfo-成员 
ConstructorInfo-结构 
FieldInfo-字段 
MethodInfo-方法 
PropertyInfo-属性 
EventInfo-事件




参考文档

1、MS官方文档:​​https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members​



【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

EFTJ6596AiAP