C#版本和Unity的关系

1.各Unity支持的的C#版本说明:主要是不同Unity版本 使用的 C#编译器和脚本运行时版本不同,所以随着Unity的更新,它一般会采用较新的 编译器和运行时版本,新版本的脚本运行时将为Unity带来了大量的新版C#功能和.NET的功能,就意味着它可以支持更高版本的C#,以下是各版本支持的C#版本;

  • Unity 2021.2 —— C# 9;
  • Unity 2020.3 —— C# 8;
  • Unity 2019.4 —— C# 7.3;
  • Unity 2018.3 —— C# 7
  • Unity 2017 —— C# 6;
  • Unity 5.5 —— C# 4;

Unity - 手动:C# 编译器 (unity3d.com)

2.Unity的.Net API兼容级别:在PlayerSetting->Other Setting->Api Compatibility Level中,我们可以设置.Net API的兼容级别:

  • .Net 4.x(特殊需求时):具备较为完整的.Net API,甚至包含了一些无法跨平台的API,如果你的应用主要针对Windows平台,并且会使用到.Net Standard 2.0中没有的功能时,选择使用它;
  • .Net Standard 2.0(建议使用):是一个.Net标准API集合,相对.Net 4.x包含更少的内容,可以减小最终可执行文件大小,它具有更好的跨平台支持;

C# 1~4功能和语法

1.C#1~4语法说明:

  • C# 1 —— 委托、事件(C#进阶)
  • C# 2 —— 泛型、匿名方法、迭代器、可空类型(C#进阶)
  • C# 3 ——隐式类型、对象集合初始化、Lambda表达式、匿名类型(C#进阶);自动实现属性、拓展方法、分部类(C#核心);Linq相关的表达式树(有时间再学);
  • C# 4 ——泛型的协变和逆变(C#进阶)以及命名和可选参数和动态类型(下面讲的);

2.C#4中的命名和可选参数:有了命名参数,我们将不用匹配参数在所调用方法中的顺序,每个参数可以按照参数名字进行指定,例如Test(f:3.3f,i:5,b:false),命名参数可以配合可选参数使用,让我们做到跳过其中的默认参数直接赋值后面的默认参数,可以让我们更方便的调用函数,少写一些重载函数;

3.C#4中的动态类型:动态类型可以节约代码量,可以理解成是当不确定对象类型,但是确定对象成员时,可以使用动态类型;

  • 关键词:dynamic;
  • 作用:通过dynamic类型标识变量的使用和对其成员的引用绕过编译时类型检查,改为在运行时解析这些操作,在大多数情况下,dynamic类型和object类型行为类似,任何非Null表达式都可以转换为dynamic类型,dynamic类型和object类型不同之处在于,编译器不会对包含类型 dynamic 的表达式的操作进行解析或类型检查,编译器将有关该操作信息打包在一起,之后这些信息会用于在运行时评估操作,在此过程中,dynamic 类型的变量会编译为 object 类型的变量,因此,dynamic 类型只在编译时存在,在运行时则不存在;
  • 注意事项说明:
    • 使用dynamic功能 需要将Unity的.Net API兼容级别切换为.Net 4.x;
    • IL2CPP 不支持 C# dynamic 关键字。它需要 JIT 编译,而 IL2CPP 无法实现
    • 动态类型是无法自动补全方法的,我们在书写时一定要保证方法的拼写正确性

C# 5功能和语法

1.C# 5新增语法说明:

  • 调用方信息特性(C#进阶——特性)
  • 异步方法async和await(C#进阶)

C# 6功能和语法

1.C# 6新增语法说明:

  • =>运算符(C#进阶——特殊语法 =>);
  • .Null 传播器(C#进阶——特殊语法 ?);
  • 字符串内插(C#进阶——特殊语法 $);
  • 静态导入(下面提及);
  • 异常筛选器(下面提及);
  • nameof运算符(下面提及);

2.C# 6静态导入:例如using static UnityEngine.Mathf简化后面的使用;

  • 用法:在引用命名空间时,在using关键字后面加入static关键词;
  • 作用:无需指定类型名称即可访问其静态成员和嵌套类型;
  • 好处:节约代码量,可以写出更简洁的代码

3.C# 6异常筛选器:例如when (e.Message.Contains(“404”))判断是否包含404字符串;

  • 用法:在异常捕获语句块中的Catch语句后通过加入when关键词来筛选异常<when(表达式)该表达式返回值必须为bool值,如果为ture则执行异常处理,如果为false,则不执行;
  • 作用:用于筛选异常;
  • 好处:帮助我们更准确的排查异常,根据异常类型进行对应的处理

4.C# 6nameof运算符:例如print(nameof(list.Count))打印的是Count这个字符串;

  • 用法:nameof(变量、类型、成员)通过该表达式,可以将他们的名称转为字符串;
  • 作用:可以得到变量、类、函数等信息的具体字符串名称,可以配合反射使用;

C# 7功能和语法

1.C# 7新增语法说明:

  • 字面值改进(下面提及);
  • out参数相关 和 弃元知识点(下面提及);
  • ref 返回值(下面提及);
  • 本地函数(下面提及);
  • 抛出表达式(下面提及);
  • 元组(下面提及);
  • 模式匹配(下面提及);

2.C# 7字面值改进:例如int i = 9_9123_1239更加方便观看,但是不影响实际数值;

  • 基本概念:在声明数值变量时,为了方便查看数值,可以在数值之间插入_作为分隔符;
  • 主要作用:方便数值变量的阅读;

3.C# 7out参数相关 和 弃元知识点:

  • 用法:不需要再使用带有out参数的函数之前,声明对应变量,例如Calc(out int x)直接声明,后面也可以使用;
    • 结合var类型更简便(但是这种写法在存在重载时不能正常使用,必须明确调用的是谁),例如Calc(out var b);
    • 可以使用_弃元符号 省略不想使用的参数,例如Calc(out _);
  • 作用:简化代码,提高开发效率

4.C# 7ref 返回值:

  • 基本概念:使用ref修饰临时变量和函数返回值,可以让赋值变为引用传递,如下主要使用几种场景:
    • 修饰值类型临时变量:例如ref int testI2 = ref testI = 900,值类型的改变和引用类型改值一样了;
    • 获取对象中的参数: ref int atk = ref r.atk = 99,同上说明;
    • 函数返回值:同上做法;
  • 作用:用于修改数据对象中的某些值类型变量

5.C# 7本地函数:

  • 基本概念:在函数内部声明一个临时函数,如下注意:
    • 本地函数只能在声明该函数的函数内部使用;
    • 本地函数可以使用声明自己的函数中的变量;
  • 作用:方便逻辑的封装;
  • 建议:把本地函数写在主要逻辑的后面,方便代码的查看;

抛出表达式

1.throw表达式回顾:抛出表达式,就是指抛出一个错误,一般的使用方式都是throw后面 new 一个异常类,异常基类是Exception,例如throw new NullReferenceException(“出错了”),以下是常见的异常类;

  • IndexOutOfRangeException:当一个数组的下标超出范围时运行时引发;
  • NullReferenceException:当一个空对象被引用时运行时引发;
  • ArgumentException:方法的参数是非法的;
  • ArgumentNullException: 一个空参数传递给方法,该方法不能接受该参数;
  • ArgumentOutOfRangeException: 参数值超出范围;
  • SystemException:其他用户可处理的异常的基本类;
  • OutOfMemoryException:内存空间不够;
  • StackOverflowException 堆栈溢出;
  • ArithmeticException:出现算术上溢或者下溢;
  • ArrayTypeMismatchException:试图在数组中存储错误类型的对象;
  • BadImageFormatException:图形的格式错误;
  • DivideByZeroException:除零异常;
  • DllNotFoundException:找不到引用的DLL;
  • FormatException:参数格式错误;
  • InvalidCastException:使用无效的类;
  • InvalidOperationException:方法的调用时间错误
  • MethodAccessException:试图访问思友或者受保护的方法;
  • MissingMemberException:访问一个无效版本的DLL;
  • NotFiniteNumberException:对象不是一个有效的成员;
  • NotSupportedException:调用的方法在类中没有实现;
  • InvalidOperationException:当对方法的调用对对象的当前状态无效时,由某些方法引发。

2.C# 7新增的更多表达式错误抛出:

  • 空合并操作符后用throw:例如str ?? throw new ArgumentNullException(nameof(str));
  • 三目运算符后面用throw:例如index ? strs[index] : throw new IndexOutOfRangeException();
  • =>符号后面直接throw:例如Action action = () => throw new Exception(“错了,不准用这个委托”);

元组

1.元组说明:

  • 基本概念:多个值的集合,相当于是一种快速构建数据结构类的方式,一般在函数存在多返回值时可以使用元组 (返回值1类型,返回值2类型,….) 来声明返回值,在函数内部返回具体内容时通过 (返回值1,返回值2,….) 进行返回;
  • 主要作用:提升开发效率,更方便的处理多返回值等需要用到多个值时的需求;

2.使用案例:

1.无变量名元组的声明(获取值:Item'N'作为从左到右依次的参数,N从1开始)
(int, float,bool,string) yz = (1, 5.5f, true, "123");
print(yz.Item1);



2.有变量名元组的声明
(int i, float f, bool b, string str) yz2 = (1, 5.5f, true, "123");
print(yz2.i);



3.元组可以进行等于和不等于的判断
//数量相同才比较,类型相同才比较,每一个参数的比较是通过==比较 如果都是true 则认为两个元组相等
if (yz == yz2)
    print("相等");
else
    print("不相等");
    
    
    
4.元组的应用——函数返回值
//利用元组定义一个多返回的函数
    private (string str, int i, float f) GetInfo(){return ("123", 2, 5.5f);}
//元组的解构赋值
(string myStr,int myInt,float myFloat) = GetInfo();
//丢弃参数,利用传入 下划线_ 达到丢弃该参数不使用的作用
(string ss, _, _) = GetInfo();


5. 元组的应用——字典
//字典中的键 需要用多个变量来控制
Dictionary<(int i, float f), string> dic = new Dictionary<(int i, float f), string>();
dic.Add((1, 2.5f), "123");

模式匹配

1.模式匹配说明:

  • 基本概念:“模式匹配”是一种测试表达式是否具有特定特征的方法,在编程里指的是,把一个不知道具体数据信息的内容,通过一些固定的语法格式来确定模式数据的具体内容的过程,是一种语法元素,可以测试一个值是否满足某种条件,并可以从值中提取信息,在C#7中,模式匹配增强了两个现有的语言结构:
    • is表达式可以在右侧写一个模式语法,而不仅仅是一个类型;
    • switch语句中的case ;
  • 主要作用:节约代码量,提高编程效率

2.详细案例

1.常量模式(is 常量):用于判断输入值是否等于某个值
        object o = 1.5f;
        if(o is 1)
        {
            print("o是1");
        }
        if(o is null)
        {
            print("o是null");
        }




2.类型模式(is 类型 变量名、case 类型 变量名):用于判断输入值类型,如果类型相同,将输入值提取出来
//判断某一个变量是否是某一个类型,如果满足会将该变量存入你申明的变量中

        if (o is int i)print(i);

        switch (o)
        {
            case int value:
                print("int:" + value);
                break;
            case float value:
                print("float:" + value);
                break;
            case null:
                print("null");
                break;
            default:
                break;
        }
  
  

3.var模式:用于将输入值放入与输入值相同类型的新变量中,相当于是将变量装入一个和自己类型一样的变量中;
        if (GetInt() is var k && k >= 0 && k <= 10)

C# 8功能和语法

1.C# 8新增语法说明:

  • 静态本地函数(下面提及);
  • Using 声明(下面提及);
  • Null 合并赋值(下面提及);
  • 解构函数Deconstruct (下面提及);
  • 模式匹配增强功能(下面提及);

2.C# 8静态本地函数:和本地函数基本一致;

  • 基本概念:静态本地函数就是在本地函数前方加入静态关键字,让本地函数不能够使用访问封闭范围内(也就是上层方法中)的任何变量;
  • 作用:让本地函数只能处理逻辑,避免让它通过直接改变上层变量来处理逻辑造成逻辑混乱;

3.C# 8Using 声明:例如using StreamWriter s2 = new StreamWriter(“文件路径”);

  • 回顾using()语法:using(对象声明){},当语句块结束 会自动帮助我们调用 对象的 Dispose这个方法 让其进行销毁;
  • 基本概念:Using 声明就是对using()语法的简写,会在上层语句块执行结束时释放该对象;
  • 注意事项:
    • 使用该简化关键字前提是有上层语句块,if,函数都是;
    • 使用using语法用在自定义类时,声明的对象必须继承System.IDisposable接口,因为必须具备Dispose方法,所以当声明没有继承该接口的对象时会报错;

4.C# 8Null 合并赋值:例如str ??= “1111”;

  • 基本概念:类似复合运算符,左边值 ??= 右边值,当左侧为空时才会把右侧值赋值给变量;
  • 注意事项:由于左侧为空才会讲右侧赋值给变量,所以不为空的变量不会改变;

5.C# 8解构函数Deconstruct(C# 7就有了):例如类中定义了该函数的话直接解构 (string name, bool sex) = p;

  • 语法:在类的内部申明函数public void Deconstruct(out 变量类型 变量名, out 变量类型 变量名…..);
  • 基本概念:我们可以在自定义类当中声明解构函数,这样我们可以将该自定义类对象利用元组的写法对其进行变量的获取,相当于把不同的成员变量拆分到不同的临时变量中;
  • 特点:一个类中可以有多个Deconstruct,但是参数数量不能相同;

模式匹配增强功能

1.模式匹配增强功能——Switch表达式:
        //switch表达式是对有返回值的switch语句的缩写
        //用=>表达式符号代替case:组合
        //用_弃元符号代替default
        //它的使用限制,主要是用于switch语句当中只有一句代码用于返回值时使用
        //语法:
        // 函数声明 => 变量 switch
        //{
        //常量=>返回值表达式,
        //常量=>返回值表达式,
        //常量=>返回值表达式,
        //....
        //_ => 返回值表达式,
        //}
        //例如一个根据枚举获取方向的方法
    public Vector2 GetPos(PosType type) => type switch
    {
        PosType.Top_Left => new Vector2(0, 0),
        PosType.Top_Right => new Vector2(1, 0),
        PosType.Bottom_Left => new Vector2(0, 1),
        PosType.Bottom_Right => new Vector2(1, 1),
        _ => new Vector2(0, 0)
    };


2.模式匹配增强功能——属性模式:
        //就是在常量模式的基础上判断对象上各属性
        //用法:变量 is {属性:值, 属性:值}
        if (info is { discount: "6折", isDiscount: true })print("信息相同");

 	//可以利用属性模式 结合 switch表达式 判断n个条件是否满足
    public float GetMoney(DiscountInfo info, float money) => info switch
    {
        { discount: "5折", isDiscount: true } => money * .5f,
        { discount: "6折", isDiscount: true } => money * .6f,
        { discount: "7折", isDiscount: true } => money * .7f,
        _ => money
    };


3.模式匹配增强功能——元组模式:
        //属性模式我们可以在switch表达式中判断多个变量同时满足再返回什么
        //但是它必须是一个数据结构类对象,判断其中的变量
        //而元组模式可以更简单的完成这样的功能,我们不需要声明数据结构类,可以直接利用元组进行判断
    //元组模式简单用法
    if((ii, bb) is (11, true))
    
    //元组结合switch模式使用
    public float GetMoney(string discount, bool isDiscount, float money) => (discount, isDiscount) switch
    {
        ("5折", true) => money * .5f,
        ("6折", true) => money * .6f,
        ("7折", true) => money * .7f,
        _ => money
    };



4.模式匹配增强功能——位置模式:
        //如果自定义类中实现了解构函数
        //那么我们可以直接用对应类对象与元组进行is判断
        if(info is ("5折", true))

	//同样我们也可以配合switch表达式来处理逻辑
    public float GetMoney(DiscountInfo info, float money) => info switch
    {
		//补充:配合when关键字进行逻辑处理
        ("5折", true) when money > 100 => money * .5f,
        ("6折", true) => money * .6f,
        ("7折", true) => money * .7f,
        _ => money
    };

C# 其他知识补充

日期和时间