CS5L10——CSharp 8 功能和语法

C# 8 对应的Unity版本

Unity 2020.3 —— C# 8
但是部分新内容还不在该版本Unity中被支持,这里筛选了一些比较实用的内容给大家讲解

本章代码关键字

1
2
3
4
5
//不愧是C#,语法糖爆炸多,本章知识不建议看这里的关键字,尤其是模式匹配,你在这里看是看不懂的XD
using //using()语法的简写,当上层语句块执行完毕时会调用对象的Dispose()方法,以释放对象
??= //空合并赋值运算符,当左侧变量为空时会把右侧值赋值给变量
Deconstruct() //类的解构函数,用于将类的不同的成员变量拆分到不同的临时变量中
when //when可以用于模式匹配里switch表达式内,添加更多的判断条件

C# 8 的新增功能和语法

  1. Using​ 声明
  2. 静态本地函数
  3. Null​ 合并赋值
  4. 解构函数 Deconstruct
  5. 模式匹配增强功能

静态本地函数

知识回顾:在C# 7的新语法中我们学习了本地函数
本地函数知识回顾:
基本概念: 在函数内部声明一个临时函数
注意: 本地函数只能在声明该函数的函数内部使用,本地函数可以使用声明自己的函数中的变量
作用: 方便逻辑的封装
建议: 把本地函数写在主要逻辑的后面,方便代码的查看

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void Start()
{
print(CalcInfo(10));
}

public int CalcInfo(int i)
{
bool b = false;
i += 10;
Calc();
StaticCalc(ref i, ref b);
return i;
//这是静态本地函数,它不能直接使用上次函数的变量,这种限制的添加是为了防止本地函数改变上次函数变量导致的逻辑混乱
static void StaticCalc(ref int num, ref bool isRight)
{
num += 10;
isRight = true;
}
//这是非静态的本地函数,这种本地函数可以直接使用上层函数的变量
void Calc()
{
i += 10;
b = true;
}
}

using 声明

知识回顾:在数据持久化xml相关知识当中,我们学习了using相关的知识点

1
2
3
4
5
6
using (对象声明)
{
//使用对象,语句块结束后 对象将被释放掉
//当语句块结束 会自动帮助我们调用 对象的 Dispose这个方法 让其进行销毁
//using一般都是配合 内存占用比较大 或者 有读写操作时 进行使用的
}

举例:

1
2
3
4
5
6
7
8
using (StreamWriter stream = new StreamWriter("文件路径"))
{
stream.Write(true);
stream.Write(1.2f);
stream.Flush();
stream.Close();
}
//语句块接收执行时,会调用对象的Dispose方法,以释放对象

新知识点:
Using​ 声明就是对 using()​ 语法的简写,当函数执行完毕时会调用对象的 Dispose​ 方法,释放对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void Start()
{
using StreamWriter stream = new StreamWriter("文件路径");
stream.Write(true);
stream.Write(1.2f);
stream.Flush();
stream.Close();
//利用这个写法 就会在上层语句块执行结束时释放该对象
} //到这里对象就释放了

//之所以说是上层语句块,是因为类似于写在if语句块里的using声明,申明的对象在if语句块结束后也会释放
void Test()
{
if (true)
{
using StreamWriter stream = new StreamWriter("文件路径");
stream.Write(1.2f);
stream.Flush();
stream.Close();
}
//到这里对象就释放了
}

注意:在使用****​using语法时,声明的对象必须继承****​System.IDisposable接口,因为必须具备****​Dispose方法,所以当声明没有继承该接口的对象时会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using UnityEngine;

public class TestUsing : IDisposable
{
public void Dispose()
{
//在这里写释放对象的逻辑
}
}

public class Lesson10 : MonoBehaviour
{
void Start()
{
using TestUsing t = new TestUsing();
}
}

Null 合并赋值

知识回顾:在C#进阶的特殊语法知识点中我们学习了 ??​ 空合并操作符
回顾空合并操作符知识点 左边值 ?? 右边值
如果左边值为 null​ 就返回右边值,否则返回左边值,只要是可以为null​的类型都能用
举例:

1
2
3
string str = null;
string str2 = str ?? "234";
print(str2);

新知识点:
空合并赋值是C# 8 新加的一个运算符 ??=
类似复合运算符,即左边值 ??= 右边值​,当左侧为空时才会把右侧值赋值给变量
举例:

1
2
3
4
5
6
7
8
9
string str = null;
string str2 = str ?? "234"; //str2被"234"赋值,因为这里的str是null
print(str2); //输出234
//当str为空时,str会被赋值为"456",这里的str是null,因此这里会赋值
str ??= "456";
print(str); //输出456
//当str为空时,str会被赋值为"1111",这里的str是456,而不是null,因此这里不会赋值
str ??= "1111";
print(str); //输出456

注意:由于左侧为空才会将右侧赋值给左侧变量,所以不为空的变量不会改变

1
2
3
4
5
6
7
8
9
//str2 = str ?? "234"就等价于下面的代码
if (str != null)
str2 = str
else
str2 = "234"
//-----------------分割线-----------------
//str ??= "123"就等价于下面的代码
if (str == null)
str = "123"

解构函数 Deconstruct()

知识回顾:我们之前学习过元组的解构,就是可以用单独的变量存储元组的值,相当于把多返回值元组拆分到不同的变量中
举例回顾:

1
2
3
4
5
6
7
8
9
void Start()
{
(i, f, _, s) = GetInfo();
}

public (int, float, bool, string) GetInfo()
{
return (1, 3.4f, true, "123");
}

新知识点:解构函数 Deconstruct()​ (C# 7就有了)
我们可以在自定义类当中声明解构函数,这样我们可以将该自定义类对象利用元组的写法对其进行变量的获取
语法: 在类的内部申明函数 public void Deconstruct(out 变量类型 变量名, out 变量类型 变量名.....)
特点: 一个类中可以有多个 Deconstruct()​,但是参数数量不能相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Person
{
public string name;
public bool sex;
public string number;
public string email;
//这是一个解构函数,通过外部传入参数,将自己的成员变量赋值给外部的变量
public void Deconstruct(out string name, out bool sex)
{
name = this.name;
sex = this.sex;
}
//一个类可以有多个解构函数,但是参数数量必须不同
public void Deconstruct(out string name, out bool sex, out string number)
{
name = this.name;
sex = this.sex;
number = this.number;
}

public void Deconstruct(out string name, out bool sex, out string number, out string email)
{
name = this.name;
sex = this.sex;
number = this.number;
email = this.email;
}
}

public class Lesson10 : MonoBehaviour
{
void Start()
{
Person p = new Person();
p.name = "SevenL";
p.sex = false;
p.email = "UnityLearning@qq.com";
p.number = "12312312312";
//用元组来接收来接收该类解构函数返回的多返回值,这里就调用了该类的两个参数的解构函数
(string name, bool sex) = p;
//上面的语句等价于:p.Deconstruct(out string name, out bool sex);
print(name); //输出SevenL
print(sex); //输出False

string number;
(_, _, number) = p;
//上面的语句等价于:p.Deconstruct(out _, out _, out number);
print(number); //输出12312312312
}
}

我们可以对该对象利用元组将其具体的变量值解构出来,相当于把不同的成员变量拆分到不同的临时变量中

注意,以上的解构函数声明还可以用lambda表达式更进一步的简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person
{
public string name;
public bool sex;
public string number;
public string email;

public void Deconstruct(out string name, out bool sex) => (name, sex) = (this.name, this.sex);

public void Deconstruct(out string name, out bool sex, out string number)
=> (name, sex, number) = (this.name, this.sex, this.number);

public void Deconstruct(out string name, out bool sex, out string number, out string email)
{
name = this.name;
sex = this.sex;
number = this.number;
email = this.email;
}
}

模式匹配

模式匹配(Pattern Matching),“模式匹配”是一种测试表达式是否具有特定特征的方法
在编程里指的是,把一个不知道具体数据信息的内容,通过一些固定的语法格式来确定模式数据的具体内容的过程

以前学习过的模式匹配

学习过的模式匹配回顾

  1. 常量模式 (is 常量)​:

    用于判断输入值是否等于某个值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //我们目前学习过的模式匹配
    //1.常量模式(is 常量):用于判断输入值是否等于某个值
    object o = 1.5f;
    if (o is 1)
    {
    print("o是1");
    }
    if (o is null)
    {
    print("o是null");
    }
  2. 类型模式 (is 类型 变量名​、case 类型 变量名​):

    用于判断输入值类型,如果类型相同,将输入值提取出来
    判断某一个变量是否是某一个类型,如果满足会将该变量存入你申明的变量中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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​ 模式:

    用于将输入值放入与输入值相同类型的新变量中,相当于是将变量装入一个和自己类型一样的变量中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int GetInt()
    {
    return 1;
    }

    if (o is var v)
    {
    print(v);
    }
    //旧写法
    int kk = GetInt();
    if (kk >= 0 && kk <= 10) { }
    //新写法,这里旧省略了kk这个中间变量,这里还省略了类型转换,由编译器直接帮我们判断这个函数返回的是何种类型,并将该过程直接并入表达式内
    if (GetInt() is var k && k >= 0 && k <= 10) { }

模式匹配增强功能

  1. switch表达式

    switch​表达式是对有返回值的switch​语句的缩写,=>表达式符号代替case:组合,用_弃元符号代替default
    由于其使用限制,这个表达式主要是用于switch​语句当中只有一句代码用于返回值时使用
    语法:

    1
    2
    3
    4
    5
    6
    7
    8
    函数声明 => 变量 switch
    {
    常量=>返回值表达式,
    常量=>返回值表达式,
    常量=>返回值表达式,
    ....
    _ => 返回值表达式,
    }

    举例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    using UnityEngine;

    //屏幕的顶角方位类型
    public enum PosType
    {
    Top_Left, //左上角
    Top_Right, //右上角
    Bottom_Left, //左下角
    Bottom_Right, //右下角
    }

    public class Lesson11 : MonoBehaviour
    {
    //获取某个屏幕的某个顶角的坐标,旧写法
    //public Vector2 GetPos(PosType type)
    //{
    // switch (type)
    // {
    // case PosType.Top_Left:
    // return new Vector2(0, 0);
    // case PosType.Top_Right:
    // return new Vector2(1, 0);
    // case PosType.Bottom_Left:
    // return new Vector2(0, 1);
    // case PosType.Bottom_Right:
    // return new Vector2(1, 1);
    // default:
    // return new Vector2(0, 0);
    // }
    //}

    //获取某个屏幕的某个顶角的坐标,新写法,可以发现type写到了switch前面,而“case 常量:”都被缩减为了“常量 => ”
    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 {属性: 值, 属性: 值}​​

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    using UnityEngine;

    public class DiscountInfo
    {
    public string discount;
    public bool isDiscount;

    public DiscountInfo(string discount, bool isDiscount)
    {
    this.discount = discount;
    this.isDiscount = isDiscount;
    }
    }

    public class Lesson11 : MonoBehaviour
    {
    void Start()
    {
    DiscountInfo info = new DiscountInfo("5折", true);
    if (info is { discount: "5折", isDiscount: true })
    print("信息相同");
    //上面等同于下面这种写法
    if (info.discount == "5折" && info.isDiscount == true)
    print("信息相同");
    }
    }

    它可以结合 switch​ 表达式使用,结合 switch​ 使用可以通过属性模式判断条件的组合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    using UnityEngine;

    public class DiscountInfo
    {
    public string discount;
    public bool isDiscount;

    public DiscountInfo(string discount, bool isDiscount)
    {
    this.discount = discount;
    this.isDiscount = isDiscount;
    }
    }

    public class Lesson11 : MonoBehaviour
    {
    void Start()
    {
    DiscountInfo info = new DiscountInfo("5折", true);
    print(GetMoney(info, 100)); //输出50
    }

    public float GetMoney(DiscountInfo info, float money) => info switch
    {
    //可以利用属性模式,结合switch表达式,判断n个条件满足,较if...else if...else的写法更为简洁
    { discount: "5折", isDiscount: true } => money * .5f,
    { discount: "6折", isDiscount: true } => money * .6f,
    { discount: "7折", isDiscount: true } => money * .7f,
    _ => money
    };
    }
  3. 元组模式

    通过刚才学习的属性模式,我们可以在 switch​ 表达式中判断多个变量同时满足再返回什么
    但是它必须是一个数据结构类对象,判断其中的变量
    而元组模式可以更简单的完成这样的功能,我们不需要声明数据结构类,可以直接利用元组进行判断

    基本使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int ii = 10;
    bool bb = true;
    if ((ii, bb) is (10, true))
    {
    print("元组的值相同");
    }

    //等价于下面这两种写法
    if (ii is 10 && bb is true)
    print("元组的值相同");
    if (ii is int && ii == 10 && bb is bool && bb)
    print("元组的值相同");

    使用 switch​ 表达式判断中判断多个变量同时满足再返回什么:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    void Start()
    {
    print(GetMoney("5折", true, 200)); //输出100
    }

    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
    };
    //上面的写法等同于下面的写法,可以发现(discount, isDiscount)被移到了switch前面,而“case 常量:”都被缩减为了“常量 => ”
    //public float GetMoney(string discount, bool isDiscount, float money)
    //{
    // switch ((discount, isDiscount))
    // {
    // case ("5折", true):
    // return money * .5f;
    // case ("6折", true):
    // return money * .6f;
    // case ("7折", true):
    // return money * .7f;
    // default:
    // return money;
    // }
    //}

    该模式匹配可以配合 when​ 关键字来进行一定的逻辑判断

  4. 位置模式

    如果自定义类中实现了解构函数,那么我们可以直接用对应类对象与元组进行 is​ 判断

    基本使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    using UnityEngine;

    public class DiscountInfo
    {
    public string discount;
    public bool isDiscount;

    public DiscountInfo(string discount, bool isDiscount)
    {
    this.discount = discount;
    this.isDiscount = isDiscount;
    }

    public void Deconstruct(out string discount, out bool isDiscount) => (discount, isDiscount) = (this.discount, this.isDiscount);
    }

    public class Lesson11 : MonoBehaviour
    {
    void Start()
    {
    DiscountInfo info = new DiscountInfo("5折", true);

    if (info is ("5折", true))
    {
    print("位置模式满足条件");
    }
    }
    }

    同样我们也可以配合 switch​ 表达式来处理逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    using UnityEngine;

    public class DiscountInfo
    {
    public string discount;
    public bool isDiscount;

    public DiscountInfo(string discount, bool isDiscount)
    {
    this.discount = discount;
    this.isDiscount = isDiscount;
    }

    public void Deconstruct(out string discount, out bool isDiscount) => (discount, isDiscount) = (this.discount, this.isDiscount);
    }

    public class Lesson11 : MonoBehaviour
    {
    void Start()
    {
    DiscountInfo info = new DiscountInfo("5折", true);
    print(GetMoneyNum(info, 300)); //输出150
    }

    public float GetMoneyNum(DiscountInfo info, float money) => info switch
    {
    ("5折", true) => money * .5f,
    ("6折", true) => money * .6f,
    ("7折", true) => money * .7f,
    _ => money
    };

    //上面的写法等同于下面的写法,可以发现info被移到了switch前面,而“case 常量:”都被缩减为了“常量 => ”
    //public float GetMoneyNum(DiscountInfo info, float money)
    //{
    // switch (info)
    // {
    // case ("5折", true):
    // return money * .5f;
    // case ("6折", true):
    // return money * .6f;
    // case ("7折", true):
    // return money * .7f;
    // default:
    // return money;
    // }
    //}
    }
  5. when(模式匹配添加判断条件)

    配合 when​ 关键字进行逻辑处理,可以添加更多的条件,以进行更细致的条件判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public float GetMoneyAdvanceFunc(DiscountInfo info, float money) => info switch
    {
    (string dis, bool isDis) when dis == "5折" && isDis => money * .5f,
    (string dis, bool isDis) when dis == "6折" && isDis => money * .6f,
    (string dis, bool isDis) when dis == "7折" && isDis => money * .7f,
    //这里的条件判断就可以添加额外的条件表达式,从而进行更细致的条件判断
    (string dis, bool isDis) when dis == "8折" && isDis && money > 100 => money * .7f,
    _ => money,
    };

    when​ 这个关键字不仅可以用于位置模式,还可以用于上面的元组模式,用于添加更多的条件

    1
    2
    3
    4
    5
    6
    7
    8
    public float GetMoney(string discount, bool isDiscount, float money) => (discount, isDiscount) switch
    {
    //这里的条件判断就可以添加额外的条件表达式,从而进行更细致的条件判断
    ("5折", true) when money > 100 => money * .5f,
    ("6折", true) => money * .6f,
    ("7折", true) => money * .7f,
    _ => money
    };