C#进阶之知识点拓展

一、.Net相关知识

1. 微软的.Net

  • 微软的.Net既不是编程语言也不是框架
  • 你可以简单为.Net本质上就是微软为自己的一系列产品取的一个代号。
  • 包含的内容:
    • 框架体系:.Net Framework、.Net Core、Mono等等。
    • 开发语言:C#,VB,F#等等(C#是.Net平台主推的开发语言)
    • 开发工具:Visual Studio、Visual Studio Code等等。

2.微软做.Net平台的目的

  • (1). 跨语言

    • 只要是面向.NET平台的编程语言(C#,VB,C++,F#等等),用其中一种语言编写的内容可以无缝地用在另一种语言编写的应用程序中。


  • (2). 跨平台

    • 一次编译,不需要任何代码修改,应用程序就可以运行在任意有.NET框架实现的操作系统上,即代码不依赖于操作系统,也不依赖硬件环境

3.如何实现的跨语言?

(1). CLS(Common Language Specification)

  • 一种公共语言规范
    • .NET专门参考每种语言并找出了语言间的共性,定义了一组规则。
    • 与其说是规则,不如说它是一组语言互操作的标准规范
    • 只要开发者都遵守这个规则来编码,那么代码就能被任意.NET平台支持的语言通用,即可以通过不同的编程语言(C#、VB、J#等等)来创建应用程序。

(2).CTS(Common Type System)

  • 公共类型系统

    • 当你需要设计面向.Net的语言时需要遵循一个体系,这个体系就是CTS。刚才提到的CLS公共语言规范就是CTS公共类型系统的子级。


    • 一个编程语言,如果==它能够支持CTS,那么我们就称它为面向.NET平台的语言。


(3). CLI(Common Language Infrastructure)

  • 公共语言基础结构
    • 是微软将CTS等内容提交给国际组织计算机制造联合会ECMA的一个工业标准。

(4). 总结

  • 微软为了实现跨语言,指定了一些规范。
  • 只要一门语言支持CTS(公共类型系统)的规则,那么我们就能够使用它在.Net平台下开发应用程序。
  • CLS(公共语言规范)是CTS的一个子级,是一组语言互操作的标准规范
  • 我们经常可以看到的CLI公共语言基础结构,它包含CTS公共类型系统。
  • 它是微软将CTS等内容提交给国际组织计算机制造联合会ECMA的一个工业标准;

4.如何实现的跨平台


(1).Net Framework(不支持跨平台)

  • .NET Framework 是一个可以快速开发、部署网站服务及应用服务的开发框架。
  • 是Windows中的一个组件,部分开源。
  • 主要用于开发Windows下应用程序包括:
    • 公共语言进行时(CLR)
    • 虚拟执行系统
    • .NET Framework 类库等

(2). .Net Framework的体系结构

  • 总结构图

    image-20240224215251015


  • 制作应用程序

    • 编程语言+调用各种类库 进行开发 生成程序集

    • ① 只要支持CLS公共语言规范的语言,都可以用于开发Windows下各种应用程序和网站程序。

    • ② BCL(基础类库)和FCL(框架类库)是这个框架体系中为我们实现好的各种API。

    • ③ 程序集的表现就是后缀为.dll或者.exe格式的文件。其中包含最关键的信息:

      • PE头、CLR头(就是一些程序的格式信息,比如入口方法,版本号等等)。
      • CIL代码(通用中间代码):是介于源代码和机器码之间的代码。之后会通过CLR产生机器码
      • CIL代码你可以理解为由于可能使用不同的代码进行开发,所以会将这些代码翻译为统一规范的中间代码

      image-20240224220211444

      image-20240224220218860

  • CLR (让应用程序在操作系统上运行)

    • 公共语言运行时,它是.Net Framework的基础,所有的.Net技术都是建立在此上的。
    • 它是一个在执行时管理代码的代码,提供内存管理、线程管理等等核心服务,就好像一个小型的操作系统一样,所以形象的把它称为“.Net虚拟机”
    • 如果想要应用程序在目标操作系统上能够运行
      • 就必须依靠.Net提供的CLR环境来支持
      • 那就必须在操作系统上安装.Net Framework
      • 它会把程序集中的IL(中间代码)翻译成机器码最终在操作系统上运行。

  • CLR主要做的事情

    image-20240224224028405

  • 总结

    • .Net Framework 是一个主要用于跨语言开发Windows操作系统下的应用程序的框架结构。
    • 它并不支持跨平台

(4). .Net Core(可以实现跨平台)

  • 新一代的.NET Framework,是.NET Framework 的翻版实现。

  • 原理:

    • 为不同的操作系统实现对应CLR公共进行时(.Net虚拟机),这样就可以在不同的平台上,将IL(中间代码)翻译成机器码了,最终在目标操作系统上运行了

    image-20240224224716311


(5). Mono(可以跨平台,早于.Net Core)

  • 在.Net Core出现之前的跨平台框架(2004-2016的==14年的跨平台空白由Mono弥补)
  • Mono是基于.Net的CLI公共语言基础结构
  • 它不仅可以运行在Windows、MacOS、Linux等主流操作系统,甚至还可以运行在PS3、XBOX等主机平台
  • Mono的1.0版本出现在2004年。所以在.Net Core 出现之前,Mono是.Net跨平台的不二之选

  • Mono如何跨平台???
    • 先把多种语言编译成通用规范的CIL中间语言
    • 再利用CLR公共语言运行时,将这些CIL公共中间语言转换为操作系统的原生代码。(主要的目的是在各种操作系统上实现了对应的CLR内容)
    • 这样用各种不同语言编写的逻辑就能够在指定操作系统上运行了
    • 这一套规则都是在.Net Framework规则上进行修改和添加的。

(6). 总结

  • 基于.Net Framework的Mono和.Net Core
    • 他们俩都基于CLI公共语言结构和CLR公共语言运行时实现跨语言和跨台。




二、Unity跨平台的基本原理(Mono)

1. Unity和Mono的关系

  • Unity的底层是通过C/C++来完成的
  • 但是为了更方便的让开发者使用,Mono成为当时的不二之选。
  • 因为Mono同时具有跨平台和跨语言的两个特性。

2. Unity跨平台的必备概念

(1). Unity主要包括的两个部分

  • Unity Engine(引擎)
    • 提供UnityEngine.dll动态库,各平台不同,C/C++编写。包含平台相关的代码、图形API、物理引擎、灯光等等所有游戏底层的东西。

  • Unity Editor(Unity编辑器)
    • 提供UnityEditor.dll动态库,大部分由C#编写,用户脚本最初可以使用C#,JavaScript,Boo语言编写,项目代码最后由Mono编译。

(2). Mono的主要构成部分

  • C#编译器(mcs)
  • Mono Runtime 类似CLR公共语言运行时(虚拟机)
    • 包括JIT(just in time)即时编译器、AOT(Ahead of time)提前编译器、GC、类库加载器等等。
  • BCL基础类库
  • Mono类库
    • 提供很多超出.Net的一些额外功能
    • 主要用于构造各种造作系统上的应用。

image-20240225100434889


3. Unity跨平台的基本原理(Mono)

  • 如图所示,在Unity下使用各种语言进行逻辑实现,这些语言会在发布时被编译成IL中间代码
  • 最终这些代码在对应操作系统上通过Mono VM(虚拟机)真正翻译成机器码运行起来

`` image-20240225100748550


4. 基于Mono跨平台的优缺点

  • 优点:
    • 只要在不同操作系统上实现Mono VM(虚拟机),那我们能够支持的平台就会“无限”多。

  • 缺点:
    • 维护工作耗时耗力,当Unity版本更新时,Mono VM也需要维护和更新。那对于N多个平台来说,工作量是非常大的。(Unity的工作量)
    • 低版本的Mono无法支持新版本C#的强大新特性。



三、Unity跨平台基本原理(IL2CPP)

1.IL2CPP是什么

​ IL(中间代码)TO CPP(转换为C++)

  • IL2CPP是在Unity 4.6.1 P5之后的版本中加入的脚本后处理方式。
  • 简单理解:
    • 继Mono之后的一种跨平台解决方案
    • 顾名思义就是把IL中间代码转译为CPP代码(C++)
    • 想要了解它我们可以先回顾一下Mono。

2. IL2CPP跨平台原理

  • 通过IL2CPP我们可以将编译好的IL中间代码转译成C++代码,再利用各平台优化过的编译器,编译为对应平台的目标代码。
  • 区别:当生成了IL中间代码后Mono是直接通过虚拟机转译运行,而IL2CPP的步骤多了一些。它会先将IL中间代码转译为C++

,再通过各平台的C++编译器直接编译为可执行的原生汇编代码

  • 注意:
    • 虽然中间代码变为了C++,但是内存管理还是遵循C#中GC的方式。这也是为什么有一个IL2CPP VM(虚拟机)。
    • IL2CPP VM(虚拟机)存在的原因,它主要是用来完成GC管理,线程创建等服务工作,通过模拟C#来完成以上操作。

image-20240226215055452


3. Mono和IL2CPP的区别

  • Mono

      1. 构建(最终打包时)速度快。

      2. Mono编译机制是JIT即时编译,所以支持更多类库。

      3. 必须将代码发布为托管程序集(.dll文件)

      4. Mono VM 虚拟机平台麻烦,且部分平台不支持(WebGL)

      5. 由于Mono版本授权原因,C#很多新特性无法使用。

      6. IOS支持Mono,但不在允许32位的Mono应用提交到应用商店。



4. Mono和IL2CPP的使用建议

  • 由于IL2CPP的运行效率有很大的优势。
  • 所以建议大家在开发中,使用IL2CPP

5. 总结

  • 建议使用IL2CPP。

四. IL2CPP模式可能会出现的问题。

1. 安装Unity. IL2CPP打包工具

  • 在Unity hub中下载,IL2CPP打包工具。

2. IL2CPP打包存在的问题——类型裁剪

  • IL2CPP 在打包时会自动对Unity工程的DLL进行裁剪,将代码中没有引用到的类型裁剪掉
  • 这样可以达到减小发布后包的尺寸的目的。
  • 然而,在实际使用过程中,很多类型有可能会被意外的剪裁掉。
    • 造成运行时抛出找不到某个类型的异常
    • 特别是:通过反射方式在编译时无法得知函数调用,在运行时都很有可能遇到问题。
    • 举例:
      • 比如我声明了一个Test类,没有使用就会被裁剪。
      • 如何使用:Test test = new Test();\

  • 解决方案:
      1. IL2CPP处理模式时
        • 将PlayerSetting => Other Setting => Managed Stripping Level(代码剥离)设置为==Low==。
        • Disable:Mono模式下才能设置为不删除任何代码
        • Low:默认低级别,保守的删除代码,删除大多数无法被访问的代码,同时也最大程度减少剥离实际使用的代码的可能性
        • Medium:中级级别,不如低级别剥离谨慎,也不会达到高级别的极端
        • Hight:尽可能多的删除无法访问的代码,有限优化尺寸减小。如果选择该模式一般配合link.xml使用。
      2. 通过Unity提供的link.xml方式来告诉Unity引擎,哪些类型是不能够被剪裁掉的。
        • 使用方法:在Unity工程的Assets目录中(或者任何子目录中)建立一个叫link.xml的XML文件
        • 注:如果没有用到反射或者映射,是没有必要去配置剥离的配置文件的。
        • 注:最好的方法,就是直接高等级剥离,根据报错去配置
        • 注:link.xml名字一定不能改。

3. IL2CPP打包存在的问题——泛型问题


  • 解决方案:

    • 泛型类:声明一个类然后在这个类中声明一些public的泛型类变量。

    • 泛型方法:随便写一个静态方法,在这个将泛型方法在其中调用一下。这个静态方法无需被调用。

      • 这样做的目的其实就是在预先编译之前,让IL2CPP知道我们需要使用这个内容。
      • 如:
      1
      2
      3
      4
      5
      public class A{}
      public class B{}
      //显示调用
      List<A> list=new List<A>();
      List<B> list2=new List<B>();
      • 解决示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class IL2CPP_Info
      {
      public List<A> list;
      public List<B> list2;
      public List<C> list3;
      public void Test<T>(T info){}
      public static void Test()
      {
      IL2CPP_Info info=new IL2CPP_Info();
      info.Test<int>(1);
      info.Test<bool>(true);
      }
      }




五、C#版本和Unity的关系

1.各版本支持的C#版本

  • Unity 2021.2 —— C# 9
  • Unity 2020.2 —— C# 8
  • Unity 2019.4 —— C# 7.3
  • Unity 2017 —— C#6
  • Unity 5.5 —— C# 4

2. 为什么不同Unity的版本支持的C#不同?

  • 之所以不同Unity版本支持的C#版本不同。
  • 主要是不同Unity版本 使用的 C#编译器和脚本运行时版本不同
    • 比如:Unity2020.3 使用的脚本运行时版本等效于.Net 4.6,编译器为RosLyn(罗斯林编译器)
    • 所以随着Unity的更新,它一般会采用较新的编译器和运行时版本。
    • 新版本的脚本运行时将为Unity带来了大量的新版C#功能和.NET的功能。
    • 也就意味着它可以支持更高版本的C#。

3.不同版本的C#对于我们来说有什么意义?

  • 我们可以根据不同Unity支持的对应C#版本
    • 来判断我们是否可以使用C#各版本中的一些新功能用来编程。
    • 虽然我们即使我们没掌握这些功能也能正常开发。
    • 但是往往新功能可以让我们写出更简单明了的代码,可以节约代码量。

4.Unity的.Net API兼容级别

  • 设置 .Net API的兼容级别

    • 在PlayerSetting ——》Other Setting ——》API Compatibility Level中

      image-20240228104251793

    • 主要里面有两种选择:

      • (1)、.Net 4.X(特殊需求时):

        具备较为完整的.Net API,甚至包含了一些无法跨平台的API。

        如果你的应用主要针对Windows平台,并且会使用到.Net Standard 2.0 中没有的功能时,会选择使用它。

      • (2)、 .Net standard 2.0 (建议使用)

        是一个.Net标准的API集合,相对.Net 4.X 包含更少的内容,可以减少最终可执行文件大小。

        它具有更好的跨平台支持

      • 注:Net standard 2.0 配置文件大小是 .Net 4.X 配置文件的一半,所以我们尽量使用.Net Standard 2.0 。


5.总结

  • 由于新版本Unity会同时更新 Scripting RunTime(脚本运行时)和C#编译器的版本,所以随着Unity版本的提升,我们能够使用到的C#的新功能和新特性也会增加。并且,对于Net API兼容级别的认识是正常情况下,我们都会使用.Net Standard。



六、线程池

七、Task任务类

八、异步方法async和await关键字知识点

九、特殊语法

十、Lambda表达式

1. 什么是lambad表达式

  • 可以将lambad表达式 理解为匿名函数的简写
  • 它除了写法不同外
  • 使用上和匿名函数一模一样
  • 都是和委托或者事件 配合使用的。

2. lambad表达式语法

  • 匿名函数语法:

    1
    2
    3
    4
    delegate (参数列表)
    {

    };

  • lambad表达式语法:

    1
    2
    3
    4
    (参数列表)=>
    {
    //函数体
    };

3. 使用

  • (1). 无参无返回值

    1
    Action a=()=>{};
  • (2). 有参

    1
    Action a=(int value)=>{};
  • (3). 甚至参数类型都可以省略 参数类型和委托或事件容器一致。

    1
    Action <int> a3=(value)=>{};
  • (4). 有返回值

    1
    Func<string,int> a4=(value)=>{return 1;};