日期 |
修订内容 |
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#语言经过以下的过程被编译成二进制机器码。
主要是分为两个过程:第一步,将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技术需要底层系统支持动态代码生成,对操作系统来说意味着需要支持动态分配带有可写可执行权限的内存,比较不安全。
IL2CPP
这是iOS禁用JIT之后出现的解决方案,性能比Mono要快得多。
IL2CPP是一个编译工具,获得C#的IL中间码之后,通过IL2CPP技术转换成C++代码,然后各个平台的C++编译器将其直接编译成能执行的原生汇编代码。
参考链接:Unity将来时:IL2CPP是什么? - 知乎 (zhihu.com)
变量和表达式
表达式
流程控制
循环的中断
- break:终止循环
- continue:跳出本轮循环
- return:跳出循环以及包含这个循环的函数
For、Foreach、Enumerator.MoveNext
遍历方式 |
内存 |
|
for |
通过索引依次进行遍历 |
|
foreach |
迭代 |
Update中不推荐使用foreach,因为会遗留内存垃圾 |
Enumerator.MoveNext |
迭代 |
运算符
i++:先对左侧的变量进行赋值
++i: 先自增,再对变量进行赋值
i++不可以作为左值
代码:
输出效果:
关键字
- 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
(2)增删改查
代码:
输出效果:
复杂的变量类型
枚举
(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:
两种声明枚举的方式:
枚举的类型转换:
案例2:
输出效果:
数据容器
各个数据容器的比较
两对很相似的对应关系:
HashTable和dictionary都不允许键重复
值 |
键值对 |
|
object |
ArrayList |
HashTable |
泛型类 |
List<> |
Dictionary<> |
不同数据类型的特征比较:
顺序 |
长度 |
成员类型 |
描述 |
|
Array |
取决于成员的类型 |
|||
ArrayList |
劣势:需要装箱和拆箱,所以速度慢、类型不安全; 优势:跟array相比,长度可变;可存放多种类型数据 |
|||
List |
跟ArrayList相比效率更高、类型安全 |
|||
Dictionary |
键不允许重复 |
|||
HashTable |
键不允许重复 |
|||
HashSet |
值不允许重复;不允许下标索引;无序;可进行交并补差 |
|||
LinkedListNode |
通过前后指示index进行访问 |
|||
LinkedList |
不允许下标索引;每个节点都是linkedlistnode类型 |
|||
Stack |
线性 |
入栈动态扩容2倍 |
||
Queue |
线性 |
(1)补充代码:Array的成员类型
输出效果:
(2)补充代码:单链表和双向链表
输出效果:
性能比较
- 插入: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回收? |
空间和读取速度 |
空间小 读取速度快 |
空间大 读取速度慢 |
装箱 & 拆箱
- 值类型——引用类型,会需要分配内存,叫做装箱,会产生GC
- 引用类型——值类型,叫做拆箱
Class
概念
面向对象编程的基础,是对现实生活中一类具有共同特征的事物的抽象。定义类之后还需要实例化,不然不可以开始使用。如果没有设定命名空间,默认放在同一个全局名称空间,不允许类名重复。
成员变量 & 成员函数
类中包含类的成员变量和成员函数,其中静态成员变量和成员函数不能通过类的实例来进行访问,只能通过类名进行访问。
构造函数 & 析构函数
如果不手写,编译器会自动添加,需要自定义的时候可以自己写。
- 构造函数:new一个实例的时候会自动执行,如果不手动写,那么系统会自动创建,手动写了之后系统就不会创建。
- 如果用public修饰,那么类的对象可以使用这个构造函数进行实例化。
- 如果用private修饰,那么仅类的静态成员会用了,不可以再用这个构造函数来创建新的实例。
- public和private修饰的构造函数不能同时存在。
- 析构函数:当前类对象被销毁的时候执行。
构造函数&静态构造函数&私有构造函数的比较
构造函数 |
静态构造函数 |
私有构造函数 |
|
概述 |
就是泛指实例构造函数 |
非实例构造函数 |
一种特殊的实例构造函数 |
共同点 |
(1)具有初始化的作用 (2)没有返回值,声明的时候不可以用void修饰符 (3)两者可以同时存在(但是仅限于无参数的构造函数),执行的时候静态构造函数在对象实例化之前调用 |
||
功能 |
通常存在于只包含静态成员的类中,使得其他类无法创建这个类的实例,也无法继承这个类 |
||
调用时机与次数 |
每次实例化的时候调用,可以执行多次 |
在第一个实例对象创建之前被调用,只能执行一次 或者在静态成员被调用之前调用 |
|
修饰符 |
可以有修饰符,也可以有参数 |
没有任何修饰符,只能加static;也没有参数 |
代码示例:静态构造函数和构造函数同时存在的时候
输出效果:带参数的构造函数没有被执行
输出效果:开始运行的时候执行静态构造函数,然后执行两次构造函数;停止运行的时候再出现一次。这种顺序和代码中写的顺序没有关系,都是静态构造函数先执行的。
作用域 & 访问修饰符
作用域:函数内生命的变量、类对象,在执行完本函数的时候会被自动销毁,存放在栈里;但是类内声明的变量、类对象,只有类被销毁的时候,管理的变量、类对象才会被销毁,存在堆里。
访问修饰符:private(同一个类中的对象可以访问) < internal/protected(同一个程序集的对象可以访问) < protected(当前类或者继承类) internal < public
- private:当前class或者struct内部访问
- internal:同一程序集内可以访问,可以从同一编译的代码中访问
- protected:同一class或者该class的派生类中可以访问
- protected internal:同一程序集 || 另一程序集中的派生类中访问
- private protected:类内部 || 派生类中
- public:同一程序集或者其他程序集
不同类型结构的可访问性:
- 类、枚举、结构体、接口、委托:只能声明为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成员。抽象类提供了可供多个派生类共享的通用基类定义,为子类提供设计思想,配合多态用于代码架构设计。
- 特点:
- 不能被实例化
- 允许构造函数
- 允许实例成员和实例方法
- 抽象类的派生类必须实现所有的抽象方法
代码示例:
说明:抽象类E将抽象类D中的虚方法声明为了一个abstract的抽象方法,这个新的抽象方法对于其子类来说依然是虚方法。这使得其子方法无法访问D中的方法,必须提供新的方法实现。
补充知识点:抽象方法和虚方法的区别
抽象方法 |
虚方法 |
|
关键词 |
abstract |
virtual |
使用 |
只能声明在抽象类中 |
不一定 |
表现 |
没有方法体,强制派生类实现 |
有方法体,子类可以覆盖也不可以覆盖 |
是否必须重写 |
必要 |
不必要 |
- 如果一个类中定义了抽象类,那么是一定要在派生类中实现的;
- 如果定义了虚方法,不一定要实现。
密封类(Sealed)
用于类:
- 功能:
- 提高密封类成员的调用效果
- 如果用于包含虚函数的派生类,可以取消成员的虚效果
- 特点:
- 不可以被继承,不能用作基类,但是可以继承别的类
- 不能声明成抽象类
- 密封类的成员函数不得声明成sealed
用于成员:
- 如果用于包含虚函数的派生类,可以取消成员的虚效果;
- 表现:通常和override一起出现
错误案例:不是重写的虚方法,无法将其密封
面向对象
概述:
- 继承:将子类的公共属性集合起来设置为父类,以提高代码的重用度,增强可维护性,符合开闭原则;继承具有单根性,C#中一个类只能继承一个类。
- 封装:通过约束代码的可访问性,增强数据的安全性;属性是C#封装的最好体现;将一系列复杂的逻辑包装起来,别人使用的时候不需要了解其中的逻辑,只需要传入参数就可以,降低了代码的耦合程度。
- 多态:一个对象的多种状态,指同名方法在不同的环境下,反映出的不同表现。
封装
- 意义:封装被称为是面向对象的第一支柱或者原则。
- 含义:类或者结构可以指定自己的每个成员对于外部代码的可访问性,可以隐藏不希望外部访问的成员。将实现的细节封装成一个接口,调用的第三方不需要关心其细节。
用不同的方法实现父类中的方法:枚举;重载。
枚举案例:
重写案例:
继承
含义:派生自一个类的类自动包含基类的所有公共、受保护的和内部成员(不包含其构造函数和终结器)。
this & base
- base的作用域:父类中找变量和方法
- this的作用域:当前类中的变量和方法
知识点:重载和重写的区别
重载 |
重写 |
|
发生对象 |
同一个定义域(比如类)中 |
父子类之间 |
功能 |
一个参数名称方法的多种实现形式 |
实现多态 |
表现形式 |
名称相同、参数或者返回值不同 |
|
多态
当派生类从基类继承的时候,他就会获得基类的所有方法、字段、属性和事件。派生类可以对其做出不同的设计。
- 静态多态:是指在方法参数和集合或数组等位置,编译的时候,通过函数重载、运算符重载实现。
- 动态多态:是指运行的时候实现,运行的时候根据不同的对象执行一个函数的不同行为,只有运行的时候才能确定具体执行什么样的行为。
重载 & 覆盖/重写
- 覆盖/重写:发生于继承关系,通过virtual/abstract和override实现,函数名和参数都是一样的。
- 重载:发生于任何关系,函数名一样,但是函数的参数不一致,就可以叫做重载。
参数是否一致的判断维度:数量、类型、顺序
泛型
含义:C#引入了类型参数
特点:
- 声明的时候可以不指定具体类型
- 可以指定泛型类型约束,T可以是常见的数据类型+自定义的类
- 如果子类也是泛型的,那么继承的时候可以不指定具体类型
用处:处理一组功能一样,但是类型不同的任务
优势:可重用、类型安全、高效
泛型约束:可以创建自己的泛型接口、泛型类、泛型方法、泛型事件、泛型委托
- class:T必须是一个类
- struct:T必须是一个结构类型
- new () :T必须要有一个无参数的构造函数
- 基类的名字:T必须继承这个基类
- 接口的名字:T必须实现这个接口
泛型类
如果父类是泛型,继承的写法有以下三种:
接口
特点:
- 只声明接口函数,不包含实现;也就意味着接口只提供派生类应该遵循的标准结构,接口成员函数的定义是派生类的责任
- 访问修饰符必须是public
- 不可以实例化
- 可以继承其他接口
- 可以继承多个接口
用处:用于约束一些行为
接口和抽象类的比较
接口 |
抽象类 |
|
变量 |
||
构造函数 |
||
函数实现 |
允许实例函数、抽象函数、虚函数;其中实例函数和虚函数都是包含方法体的 |
|
访问修饰符 |
默认public,可以改为internal; 其成员默认是public,可以修改。 |
默认是internal的,允许public、internal。 其成员默认是private的,可以修改。 |
多重继承 |
允许 |
不允许,只能单根 |
相同点 |
实例化:都不允许实例化 函数:接口和抽象类中的抽象方法都是声明函数但是不含实现 派生类:都必须去实现接口或者抽象类的抽象方法。 |
接口和委托的比较
接口 |
委托 |
|
概念 |
约束类应该具备的功能集合 |
函数的容器,本质上是一个类 |
解决的问题 |
约束类,便于类的管理和拓展 解决类的单继承问题 |
约束函数,便于函数的管理和拓展 |
使用场景 |
多重继承 完全抽象 |
多用于事件处理中,约束事件处理器 |
结构体
结构体 |
类 |
|
变量 |
普通变量:全局变量不能再声明中直接赋值,只能在构造函数中赋值; const、readonly:结构体中readonly只能在构造函数中赋值 |
普通变量:类在哪里都可以 |
构造函数 |
||
析构函数 |
||
访问修饰符 |
函数不允许声明为virtual 类型不允许声明为abstract |
类都可以 |
使用 |
访问变量:变量不需要实例化 访问函数:需要实例化 |
访问变量:需要实例化 访问函数:需要实例化 |
底层数据结构 |
值类型 |
引用类型 |
实例化 |
值类型,不会分配堆内存,仅仅调用结构体的构造函数初始化 |
引用类型,实例化的时候会在堆上开辟内存,然后调用构造函数进行初始化 |
继承 |
不能互相继承 |
除了密封类,都可以相互继承 |
相同点 |
静态构造函数:都支持 自定义函数:都支持 const:const的使用方法都一样,都需要在声明的时候就进行赋值 |
委托
概念
本质上是一个类型
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-事件