C#面试知识点提炼整理

GC回收器

采用分代算法,有内存整理,避免碎片化。有压缩。

 简易流程

1.GC会检查堆内存上的每个存储变量;

2.对每个变量会检测其引用是否处于激活状态;

3.如果变量的引用不再处于激活状态,则会被标记为可回收;

4.被标记的变量会被移除,其所占有的内存会被回收到堆内存上。

 流程详细介绍(仅供参考)

1.当新建立引用类型对象时,检查0代储存空间是否有充足的空间使得新的引用类型对象存储。若没有,将0代对象进行遍历检查,是否有被调用(激活),没有被调用的对象被标记“可回收”。

2.遍历完成后,将所有被“可回收”的对象进行垃圾回收,释放的空间返回给0代储存区,其他的对象的对象 迁移 到1代储存区,标记为“1代对象”,此时该对象是分散分布的,要进行 压缩 操作,使得1代对象顺序紧密排列。新对象存储于0代储存空间,标记为0代对象。

3.当1代空间满了时,将1代对象按照上述操作遍历,迁移,压缩到2代储存区,标记为2代对象,同时0代迁移压缩到1代。

GC带来的问题

GC操作是一个极其耗费事件的操作,堆内存上的变量或者引用越多则导致遍历检查时的操作变得十分缓慢。

unityGC采用的是非分代非压缩的标记清除算法,GC操作会产生“内存碎片化”。使用分代压缩可以尽可能降低内存碎片化,但是其内存整理的过程也是有一定开销。当内存片段被回收时,有可能原本连续的堆内存被分割成碎片化的单元。

GC触发

1.在堆内存上进行内存分配操作,而内存不够的时候都会触发垃圾回收来利用闲置的内存;

2.GC会自动的触发,不同平台运行频率不—样;

3.GC可以被强制执行。

减少GC原则

DOTS的其中一条核心思想就是面向数据式开发,使用结构体来尽可能代替类,来减少GC的触发。

1.减少临时变量的使用,多使用公共对象,多利用缓存机制。(将容器定义到函数外,用到容器的时候进行修改即可)

2.减少new对象的次数。

3.对于大量字符串拼接时,将StringBuilder代替String。(string不可修改性,修改即创建一个新的string对象,旧的直接抛弃等待GC,但少量字符串拼接用string,性能优于stringbuilder)

4.使用扩容的容器时,例如:List,StringBuilder等,定义时尽量根据存储变量的内存大小定义储存空间,减少扩容的操作。(扩容后,旧的容器直接抛弃等待GC)

5.代码逻辑优化:例如计时器当大于1s后才进行文本修改,而不是每帧都修改,或者禁止在关键时候GC,影响游戏性能,可以在加载页面或者进度条的时候GC。

6.利用对象池:对象池是一种Unity经常用到的内存管理服务,针对经常消失生成的对象,例如子弹,怪物等,作用在于减少创建每个对象的系统开销。在我们想要对象消除时,不直接Destory,而是隐藏起来SetActive(false),放入池子中,当需要再次显示一个新的对象时,先去池子中看有没有隐藏对象,有就取出来(显示) SetActive(true),没有的话,再实例化。

7.减少装箱拆箱的操作:

 

装箱拆箱

装箱是将值类型转换成引用类型,或者是实现了接口的值类型。装箱将数据存储的空间由Thread stack转存到了Managed Heap(托管堆)中。凡是在Managed Heap中开辟空间,都将触发GC(垃圾回收),在Thread statck将不会触发垃圾回收。

简单速记:装箱就是将一个值类型装到一个类型为object引用类型或该值类型所要实现的接口的箱子里,就是说装到了一个盲盒里,我们只知道盲盒的类型,推测盲盒可能装的什么,但是不能确定箱子里的所存内存片段的具体类型。

而拆箱就是将一个盲盒拆开,我们提供一个具体值类型,把它拿出来。如果提供错了,就会抛出。拆箱的过程又会进行一次值拷贝。

注意yield return 所接受类型是一个object。 yeild return 0 会产生装箱拆箱,相同的操作可以替换为 yeild return null。

结构体与类

C#中存在初始化器和构造器,初始化器里可以自定义要初始化成员。结构体一定要初始化所有字段,否则无法使用。使用new结构体,可以自动调用构造器,如果没有定义构造器,那么c#会自动调用默认构造器,默认以默认值构造结构体的所有成员。

结构体定义有参构造函数后,无参构造仍然存在,仍可被作为构造器,这与类是不一样的。

结构体不能申明析构函数,而类可以。因为结构体不需要交由gc回收。

结构体不能被继承,而类可以。

结构体不能被静态static修饰(不存在静态结构体),而类可以

提到结构体和类,不得不提它们在RAM的分配机制。 或许你可能看过网上说“结构体存在栈中,类存在堆中”,这里告诉你这是错的,不要被误导了。至于分配是堆还是栈里还得看其所属的域(局部/全局、引用/值类型)。这里就不详细描述了,这不是这篇的重点。

结构体可以继承接口,实现接口。也可以装箱,转为接口。

接口只能声明属性和方法。更多的是一种规范、一种协议。

接口和抽象类的区别,可以看我以往的文章C#接口抽象类什么时候用?接口与抽象类的区别。_c#何时用接口-CSDN博客

静态构造函数

  1. 默认用于对静态字段、只读字段等的初始化。
  2. 静态构造函数既没有访问修饰符,也没有参数。
  3. 静态构造函数隶属于类,在程序的生命周期内只运行一次。
  4. 一个类或结构体只能有一个静态构造函数。
  5. 在创建第一个类实例或任何静态成员被引用时,.NET将自动调用静态构造函数来初始化类。
  6. 最容易忽略的是:结构体的静态构造函数只有在它的静态成员被访问时才会开始执行。

ref 与 out

ref的使用和理解-为什么要初始化,ref和out有什么区别。_ref初始化-CSDN博客

托管代码与非托管

托管代码

托管代码是在公共语言运行库中运行的代码,托管代码的执行由公共语言运行库(CLR)来直接控制,而不是操作系统来直接控制。 CLR 负责解释托管代码、将其编译成机器代码,然后执行它。具体而言,由公共语言运行库中的代码管理器来控制和执行托管代码,而公共语言运行库的其他部分会提供托管代码执行时所需要的各种服务,如垃圾回收、类型检查、安全支持等。

非托管代码

非托管代码是在托管代码的概念出现后相对于托管代码而言的,其实际上是计算机操纵系统可识别的机器码。非托管代码(机器码)的执行由操作系统来控制,其在执行时也需要各种服务,如垃圾回收、类型检查、安全支持等。由于没有像公共语言运行库这样的东西会自动给你提供服务,所以你需要自己提供这些服务,也就说你要写额外的代码来实现垃圾回收等功能。

协变与逆变

out:协变(和谐的转变) [ClassA<object>  -> ClassA<string>]

in: 逆变(逆转变) [ClassA<string>   ->  ClassA<object>]

协变和逆变是用来修饰泛型的,用于泛型中修饰字母,只有泛型接口和泛型委托能使用.

//1.返回值与参数
 
//用out修饰的泛型,只能作为返回值
 
delegate T Testout<out T>();
 
//用in修饰的泛型,只能作为参数
 
delegate void TestIn<in T>(T t);

 

 

C# 协变、逆变 看完这篇就懂了 - 知乎 (zhihu.com)

 

反射整理

反射机制:通过程序集信息、类型信息等等动态获取到项目中的程序集或程序集下的某个类型,以及类下的某个方法、字段、属性。

常用接口:

 

动态创建对象:

Activator.CreateInstance(程序集名称,类型全名,args)

Activator.CreateInstance(Type,args)

 

assembly = Assembly.Load获取程序集

assembly.CreateInstance('fullnameOfType'/Type)

 

获取泛型

assembly=GetExecutingAssembly()//  当前调用函数所在的程序集

type = assembly.GetType('typename`n')//获取泛型类型,不含泛型参数

genericType = type.MakeGeneric(Type)//获取泛型类型,并指定泛型参数类型

调用构造器

构造对象:type.GetConstructor(new Type[] { }).Invoke(obj,args);//获取构造器信息,并调用构造器 构造对象。

虚函数实现原理

每个虚函数都会有一个与之对应的虚函数表,该虚函数表的实质是一个指针数组,存放的是每一个派生类对象的虚函数入口地址。对于一个派生类来说,他会继承基类的虚函数表同时增加自己的虚函数入口地址,如果派生类重写了基类的虚函数的话,那么继承过来的虚函数入口地址将被派生类的重写虚函数入口地址替代

using的作用

资源:实现了IDisposable接口的类或结构。
using语句确保这些资源能够被适当的释放(Resource.Dispose)
using原理:using(分配资源){ 使用资源 } ——> 释放资源 (隐式)
using_百度百科 (baidu.com)

简单来说就是using申请一个资源,申请的资源只能在using这个局部域内使用,同时随局部域释放。

字典

内部用了Hashtable作为存储结构

  • 它比哈希表更快,因为没有装箱和拆箱,尤其是值类型。
  • Dictionay 是 Hashtable 的类型安全实现, Keys和Values是强类型的。
  • Dictionary遍历输出的顺序,就是加入的顺序

 

  • 哈希函数构造
    以记录为关键字为一个哈希函数提供key,将不定长的二进制数据集映射到一个较短的哈希地址上,一个Key通过哈希函数得到HashCode。
  • 解决冲突
    • 开放地址法:对相同HashCode解决冲突的方法是对HashCode在哈希表上进行探测。
    • 链地址法:拉链法的基本思想就像对冲突的元素,建立一个单链表,头指针存储在对应hashcode位置。hashcode对应后,遍历单链表比较值,最终获取值在哈希表中的映射地址。

让某个类的字段作为字典的key,要怎么操作

反射类字段,得到FieldInfo,将类名与字段名组合起来作为字典的key。

using的作用?除了引入命名空间之外还有可以做什么?

简单来说就是using申请一个资源,申请的资源只能在using这个局部域内使用,同时随局部域释放。

列表字典初始化容量

作者:Miracle
来源:麦瑞克博客
链接:https://www.unitymake.com/archives/programming-life/3476
本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议,转载请注明!
THE END
分享
打赏
海报
C#面试知识点提炼整理
GC回收器 采用分代算法,有内存整理,避免碎片化。有压缩。  简易流程 1.GC会检查堆内存上的每个存储变量; 2.对每个变量会检测其引用是否处于激活状态; 3.如果……
<<上一篇
下一篇>>
文章目录
关闭
目 录