CS5L9——CSharp 7 功能和语法
CS5L9——CSharp 7 功能和语法
C#7的新语法更新重点主要是 代码简化
今天学习的out和ref新用法,弃元、本地函数都是相对比较重要的内容,可以给我们带来很多便捷性
而元组和模式匹配知识点 是C# 7中引入的最重要的两个知识点
他们可以帮助我们更效率的完成一些功能需求,建议大家常用他们
本章代码关键字
1 | //不愧是C#,语法糖爆炸多,本章知识不建议看这里的关键字,尤其是元组,模式匹配,你在这里看是看不懂的XD |
C# 7 对应的Unity版本
Unity 2018.3支持 C# 7,Unity 2019.4支持 C# 7.3,7.1, 7.2,7.3 相关内容都是基于 7 的一些改进
C# 7 的新增功能和语法
- 字面值改进
- out 参数相关 和 弃元 知识点
- ref 返回值
- 本地函数
- 抛出表达式
- 元组
- 模式匹配
字面值改进
基本概念:在声明数值变量时,为了方便查看数值,可以在数值之间插入 _
作为分隔符
主要作用:方便数值变量的阅读,而 _
本身对程序没有影响(我们不能在数值开头使用_
)
1 | int i = 9_9123_1239; |
out变量的快捷使用 和 弃元
out的快捷使用(内联声明)
用法:不需要再使用带有 out
参数的函数之前,声明对应变量
作用:简化代码,提高开发效率
1 | void Start() |
结合 var
类型更简便
使用 var
后,编译器会根据参数需要的类型,自动帮我们判断该变量的类型
1 | Calc(out var x, out var y); |
(但是这种写法在存在重载时不能正常使用,必须明确调用的是谁)
1 | void Start() |
弃元
可以使用 _
弃元符号,省略不想使用的参数
1 | void Start() |
ref修饰临时变量和返回值
基本概念:使用 ref
修饰临时变量和函数返回值,可以让赋值变为引用传递
作用:用于修改数据对象中的某些值类型变量
-
修饰值类型临时变量:
在以前,将值类型变量a赋值给另一个值类型变量b,修改b并不能将a的值也修改掉,因为它们分别使用的是内存上不同的数据
1
2
3
4int testI = 100;
int testI2 = testI; //这里是值传递,也就是说,testI2新开辟了一个新的内存空间来存储testI的数据
testI2 = 900; //因此,在这里修改testI2并不能影响testI1的数据,因为它们指向的内存空间是不同的
print(testI);而使用
ref
关键字,对赋值的两边添加ref
,会使b指向a在内存的数据,这会导致修改b,也会修改a的值,因为它们使用的是内存上相同的数据1
2
3
4int testI = 100;
ref int testI2 = ref testI; //通过ref来修饰临时变量,使这个赋值变成了引用传递,也就是说,testI2指向的testI1的内存空间
testI2 = 900; //因此,在这里修改testI2可以改变testI1的数据,因为它们指向的内存空间是相同的
print(testI);结构体是值类型,因此使用结构体变量赋值另一个结构体变量时,会新开辟一块内存空间,因此修改新的结构体变量,并不能修改原来的结构体变量
而在在赋值时使用ref
修饰临时变量,可以使两个变量指向同一个内存,修改其中一个也能修改另一个1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public struct TestRef
{
public int atk;
public int def;
public TestRef(int atk, int def)
{
this.atk = atk;
this.def = def;
}
}
public class Lesson8 : MonoBehaviour
{
void Start()
{
TestRef r1 = new TestRef(5, 5);
ref TestRef r2 = ref r1; //用ref修饰临时变量,因此这里是引用传递,也就是说,r2和r1指向了同一个内存空间
r2.atk = 10; //因此修改r2的某个成员变量,会使r1的成员变量也被修改,因为它们指向的内存空间是相同的,类似于语言类型
print(r1.atk);
}
} -
获取对象中的参数,使变量指向该对象的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public struct TestRef
{
public int atk;
public int def;
public TestRef(int atk, int def)
{
this.atk = atk;
this.def = def;
}
}
public class Lesson8 : MonoBehaviour
{
void Start()
{
ref int atk = ref r1.atk; //通过ref修饰临时变量,使atk指向结构体变量的某个成员存储数据的内存空间
atk = 99; //因此修改atk,就会修改r1.atk的值,因为atk指向的使r1.atk的内存空间
print(r1.atk);
}
} -
函数返回值
将方法的返回值前都加上
ref
,并使用ref
修饰的变量接收返回的引用传递(不能缺少任意一个ref
,否则会导致引用传递失效或者语法错误)
这样可以使变量接收到的返回值为引用传递,或者说让变量指向返回的对象的内存空间,修改变量的值就会同时修改返回的对象的成员1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20void Start()
{
int[] numbers = new int[] { 1, 2, 3, 45, 5, 65, 4532, 12 };
//函数返回的是数组某个元素的引用,由一个ref修饰的变量接收引用传递,使该变量指向返回的元素的内存空间
ref int number = ref FindNumber(numbers, 5);
number = 98765; //修改这个变量就会修改方法返回的数组的某一个元素的值
print(numbers[4]);
}
//要使返回值为值类型的方法,返回的是某个值类型的引用,我们需要用ref来修饰该方法,并让ref来修饰return
public ref int FindNumber(int[] numbers, int number)
{
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == number)
return ref numbers[i]; //返回numbers的第i个元素的引用
}
return ref numbers[0]; //返回numbers的第一个元素的引用
}
本地函数
基本概念:在函数内部声明一个临时函数
注意:本地函数只能在声明该函数的函数内部使用,本地函数可以使用声明自己的函数中的变量
作用:方便逻辑的封装
建议:把本地函数写在主要逻辑的后面,方便代码的查看
可以理解为是本地函数就像是函数自己的私有函数,就像类的私有函数那样,
函数里可以直接调用这个本地函数,本地函数的局部变量生命周期也仅限于本地函数内部
1 | void Start() |
抛出表达式
throw
:抛出表达式,就是指抛出一个错误,一般的使用方式 都是 throw new 一个异常类
**异常基类:**Exception
1 | throw new System.Exception("出错了!"); |
C#自带的异常类
1 | //常用的异常类 |
在C# 7中,可以在更多的表达式中进行错误抛出
好处:更节约代码量
-
空合并操作符后用
throw
1
2private string jsonStr;
private void InitInfo(string str) => jsonStr = str ?? throw new System.ArgumentNullException(nameof(str)); -
三目运算符后面用
throw
1
2
3
4
5
6
7
8
9
10void Start()
{
GetInfo("1,2,3", 4); //因为第一个参数被分割后并不能获取到索引为4的子字符串,因此抛出异常
}
private string GetInfo(string str, int index)
{
string[] strs = str.Split(',');
return str.Length > index ? strs[index] : throw new System.IndexOutOfRangeException(nameof(str));
} -
=>符号后面直接
throw
1
System.Action action = () => throw new System.Exception("错误,该委托不可调用!");
元组
基本概念:
多个值的集合,相当于是一种快速构建数据结构类的方式
一般在函数存在多返回值时可以使用元组 (返回值1类型, 返回值2类型,…) 来声明返回值
在函数内部返回具体内容时通过 (返回值1, 返回值2, …) 进行返回****主要作用:
提升开发效率,更方便的处理多返回值等需要用到多个值时的需求
-
无变量名元组的声明
(获取值:Item'N'
作为从左到右依次的参数,N
从1开始)1
2
3
4
5
6
7
8
9(int, float) yz = (1, 5.5f);
print(yz.Item1);
print(yz.Item2);
(int, float,bool,string) yz1 = (1, 5.5f, true, "123");
print(yz1.Item1);
print(yz1.Item2);
print(yz1.Item3);
print(yz1.Item4); -
有变量名元组的声明
(有变量名的话,就不需要什么item1
这些,直接使用元组名+变量名获取元组的成员值)1
2
3
4
5(int i, float f, bool b, string str) yz2 = (1, 5.5f, true, "123");
print(yz2.i);
print(yz2.f);
print(yz2.b);
print(yz2.str);值得一提的是,元组赋值时也可以用类似于函数命名参数那样在参数前加上变量名,但是我们赋值时仍然不能改变赋值的顺序
1
2(int i, float f, bool b, string str) yz2 = (i: 1, f: 5.5f, b: true, str: "123");
//(int i, float f, bool b, string str) yz2 = (i: 1, b: true, f: 5.5f, str: "123"); //即使我们这样写,也不能改变赋值的顺序 -
元组可以进行等于和不等于的判断
但是,数量相同才能比较,类型相同才能比较,否则会报错! 每一个参数的比较是通过==
比较 如果都是true
则认为两个元组相等1
2
3
4
5
6
7(int i, float f, bool b, string str) yz1 = (1, 5.5f, true, "123");
(int i, float f, bool b, string str) yz2 = (1, 5.5f, true, "123");
if (yz1 == yz2)
print("相同");
else
print("不同");
元组不仅可以作为临时变量 成员变量也是可以的
1 | public class Lesson9 : MonoBehaviour |
函数多返回值
-
无变量名函数返回值
1
2
3
4
5
6
7
8
9
10
11
12void Start()
{
var info = GetInfo();
print(info.Item1);
print(info.Item2);
print(info.Item3);
}
private (string, int, float) GetInfo()
{
return ("123", 2, 5.5f);
} -
有变量名
1
2
3
4
5
6
7
8
9
10
11
12void Start()
{
var info = GetInfo();
print(info.str);
print(info.i);
print(info.f);
}
private (string str, int i, float f) GetInfo()
{
return ("123", 2, 5.5f);
} -
元组的解构赋值
相当于把多返回值元组拆分到不同的变量中
1
2
3
4
5
6
7
8
9
10
11
12void Start()
{
(string myStr, int myInt, float myFloat) = GetInfo();
print(myStr);
print(myInt);
print(myFloat);
}
private (string str, int i, float f) GetInfo()
{
return ("123", 2, 5.5f);
}或者也可以这样写,要注意不能与上面的写法混用,要么声明都写在里面,要么声明都写在外面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void Start()
{
string myStr;
int myInt;
float myFloat;
(myStr, myInt, myFloat) = GetInfo();
print(myStr);
print(myInt);
print(myFloat);
}
private (string str, int i, float f) GetInfo()
{
return ("123", 2, 5.5f);
} -
丢弃参数
利用传入 下划线
_
达到丢弃该参数不使用的作用1
2(string ss, _, _) = GetInfo();
print(ss)
字典多变量键
当字典中的键 需要用多个变量来控制时
1 | Dictionary<(int i, float f), string> dic = new Dictionary<(int i, float f), string>(); |
模式匹配
基本概念:
模式匹配是一种语法元素,可以测试一个值是否满足某种条件,并可以从值中提取信息
在C#7中,模式匹配增强了两个现有的语言结构
-
is
表达式,is
表达式可以在右侧写一个模式语法,而不仅仅是一个类型 -
switch
语句中的case
主要作用:
节约代码量,提高编程效率
-
常量模式(
is 常量
):
用于判断输入值是否等于某个值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16object o = 1;
//旧写法
if (o is int)
{
print("o是int");
}
//新写法
if (o is 1)
{
print("o是1");
}
//等价于:
if (o is int && o = 1)
{
print("o是1");
}1
2
3
4
5
6
7
8
9object o = null;
if (o is 1)
{
print("o是1");
}
if (o is null)
{
print("o是null");
} -
类型模式(
is 类型 变量名
、case 类型 变量名
):
用于判断输入值类型,如果类型相同,将输入值提取出来
判断某一个变量是否是某一个类型,如果满足会将该变量存入你申明的变量中
注意,该变量是生命周期是整个函数1
2
3
4
5
6
7
8
9
10
11object o = 1;
if ( o is int i )
{
print(i);
}
//等同于下面这个写法
//if (o is int)
//{
// int i = (int)o;
// print(i);
//}类型模式还可以用于
switch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15object o = 1;
switch (o)
{
case int value:
print("int: " + value);
break;
case float value:
print("float: " + value);
break;
case null:
print("null");
break;
default:
break;
} -
var模式:
用于将输入值放入与输入值相同类型的新变量中,相当于是将变量装入一个和自己类型一样的变量中1
2
3
4
5
6
7object o = 1;
if (o is var v)
{
print(o);
print(v);
}
//实际就等同于var v = o
var
模式可以用于让一个变量装载一个不确定类型的返回值,由编译器帮我们判断是何种类型,且该过程可以写入一句表达式内,以缩减代码行数1
2
3
4
5
6
7
8
9
10
11
12
13
14int 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) { }