Unity进阶--C#相关
目录
可选参数和动态类型
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lesson1 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//命名和可选参数
//有了命名参数,我们将不用匹配参数在所调用方法中的顺序
//每个参数可以按照参数名字进行指定
Test(1, 1.1f, true);
Test(f: 1.2f, b: false, i: 12);
//命名参数可以配合可选参数使用,让我们做到跳过其他的默认参数直接赋值后面的默认参数
Test2(1);
Test2(1, b: true);
//动态类型
// 关键词: dynamic
// 作用:通过 dynamic 类型标识变量的使用和对其成员的引用绕过编译时类型检查
// 改为在运行时解析这些操作。
// 在大多数情况下,dynamic 类型和 object 类型行为类似
// 任何非 Null 表达式都可以转换为 dynamic 类型。
// dynamic 类型和 object 类型不同之处在于,
// 编译器不会对包含类型 dynamic 的表达式的操作进行解析或类型检查
// 编译器将有关该操作信息打包在一起,之后这些信息会用于在运行时评估操作。
// 在此过程中,dynamic 类型的变量会编译为 object 类型的变量。
// 因此,dynamic 类型只在编译时存在,在运行时则不存在。
// 注意:
// ① 使用 dynamic 功能 需要将 Unity 的.Net API 兼容级别切换为.Net 4.x
// ② IL2CPP 不支持 C# dynamic 关键字。它需要 JIT 编译,而 IL2CPP 无法实现
// ③ 动态类型是无法自动补全方法的,我们在书写时一定要保证方法的拼写正确性
// 所以该功能我们只做了解,不建议大家使用
// 举例说明:
dynamic dyn = 1;
object obj = 2;
print(obj.GetType());
print(dyn.GetType());
// 好处:动态类型可以节约代码量,当不确定对象类型,但是确定对象成员时,可以使用动态类型
// 通过反射处理某些功能时,也可以考虑使用动态类型来替换它
}
public void Test(int i, float f, bool b)
{
}
public void Test2(int i, float f = 1.2f, bool b = false)
{
}
// Update is called once per frame
void Update()
{
}
}
线程和线程池
命名空间: System.Threading
类名: ThreadPool(线程池)
在多线程的应用程序开发中,频繁的创建删除线程会带来性能消耗,产生内存垃圾
为了避免这种开销 C# 推出了 线程池 ThreadPool 类
ThreadPool 中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务
任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用
当线程池中所有的线程都在忙碌时,又有新任务要处理时,线程池才会新建一个线程来处理该任务,
如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行
线程池能减少线程的创建,节省开销,可以减少 GC 垃圾回收的触发
线程池相当于就是一个专门装线程的缓存池
优点:节省开销,减少线程的创建,进而有效减少 GC 触发
缺点:不能控制线程池中线程的执行顺序,也不能获取线程池内线程取消 / 异常 / 完成的通知
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class lesson1_1 : MonoBehaviour
{
Thread t;
// Start is called before the first frame update
void Start()
{
//线程
/*
Unity中支持多线程
Unity中开启的多线程不能使用主线程中的对象
Unity中开启多线程后一定要关闭
*/
t = new Thread(() => {
while (true)
{
print("12138");
Thread.Sleep(1000);
}
});
t.Start();
print("主线程执行");
//线程池
// 命名空间: System.Threading
// 类名: ThreadPool(线程池)
// 在多线程的应用程序开发中,频繁的创建删除线程会带来性能消耗,产生内存垃圾
// 为了避免这种开销 C# 推出了 线程池 ThreadPool 类
//ThreadPool 中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务
// 任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用
// 当线程池中所有的线程都在忙碌时,又有新任务要处理时,线程池才会新建一个线程来处理该任务,
// 如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行
// 线程池能减少线程的创建,节省开销,可以减少 GC 垃圾回收的触发
// 线程池相当于就是一个专门装线程的缓存池
// 优点:节省开销,减少线程的创建,进而有效减少 GC 触发
// 缺点:不能控制线程池中线程的执行顺序,也不能获取线程池内线程取消 / 异常 / 完成的通知
//ThreadPool 是一个静态类
// 里面提供了很多静态成员
// 其中相对重要的方法有
//1. 获取可用的工作线程数和 I/O 线程数
int workerNum;
int ioNum;
ThreadPool.GetAvailableThreads(out workerNum,out ioNum);
print(workerNum + "/" + ioNum);
//2. 获取线程池中工作线程的最大数目和 I/O 线程的最大数目
ThreadPool.GetMaxThreads(out workerNum,out ioNum);
print(workerNum + "/" + ioNum);
//3. 设置线程池中可以同时处于活动状态的 工作线程的最大数目和 I/O 线程的最大数目
// 大于次数的请求将保持排队状态,直到线程池线程变为可用
// 更改成功返回 true,失败返回 false
ThreadPool.SetMaxThreads(20,20);
ThreadPool.GetMaxThreads(out workerNum, out ioNum);
print(workerNum + "/" + ioNum);
//4. 获取线程池中工作线程的最小数目和 I/O 线程的最小数目
ThreadPool.GetMinThreads(out workerNum,out ioNum);
print(workerNum + "/" + ioNum);
//5. 设置线程池中可以同时处于活动状态的 工作线程的最小数目和 I/O 线程的最小数目
ThreadPool.SetMinThreads(5,5);
ThreadPool.GetMinThreads(out workerNum, out ioNum);
print(workerNum + "/" + ioNum);
//6. 将方法排入队列以便执行,当线程池中线程变得可用时执行
ThreadPool.QueueUserWorkItem((obj) => {
print(obj);
print("开启了一个线程");
},"22222222");
}
private void OnDestroy()
{
t.Abort();
}
}
Task任务类
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
public class lesson1_2 : MonoBehaviour
{
bool isRuning = true;
Task<int> task;
Task<int> task1;
Task<int> task2;
// Start is called before the first frame update
void Start()
{
#region Task
// 命名空间: System.Threading.Tasks
// 类名: Task
//Task 顾名思义就是任务的意思
//Task 是在线程池基础上进行的改进,它拥有线程池的优点,同时解决了使用线程池不易控制的弊端
// 它是基于线程池的优点对线程的封装,可以让我们更方便高效的进行多线程开发
// 简单理解:
//Task 的本质是对线程 Thread 的封装,它的创建遵循线程池的优点,并且可以更方便的让我们控制线程
// 一个 Task 对象就是一个线程
#endregion
#region 创建无返回值 Task 的三种方式
//1. 通过 new 一个 Task 对象传入委托函数并启动
//Task task = new Task(() => {
// int i = 0;
// while (isRuning)
// {
// print("方式一:" + i++);
// Thread.Sleep(1000);
// }
//});
//task.Start();
//2. 通过 Task 中的 Run 静态方法传入委托函数
//Task task1 = Task.Run(() => {
// int i = 0;
// while (isRuning)
// {
// print("方式二:" + i++);
// Thread.Sleep(1000);
// }
//});
//3. 通过 Task.Factory 中的 StartNew 静态方法传入委托函数
//Task.Factory.StartNew(() => {
// int i = 0;
// while (isRuning)
// {
// print("方式三:" + i++);
// Thread.Sleep(1000);
// }
//});
#endregion
#region 创建有返回值的 Task
//1. 通过 new 一个 Task 对象传入委托函数并启动
task = new Task<int>(() => {
int i = 0;
while (isRuning)
{
print("方式一:" + i++);
Thread.Sleep(1000);
}
return i;
});
task.Start();
//2. 通过 Task 中的 Run 静态方法传入委托函数
task1 = Task.Run<int>(() => {
int i = 0;
while (isRuning)
{
print("方式二:" + i++);
Thread.Sleep(1000);
}
return i;
});
//3. 通过 Task.Factory 中的 StartNew 静态方法传入委托函数
task2 = Task.Factory.StartNew<int>(() => {
int i = 0;
while (isRuning)
{
print("方式三:" + i++);
Thread.Sleep(1000);
}
return i;
});
// 获取返回值
// 注意:
//Result 获取结果时会阻塞线程
// 即如果 task 没有执行完成
// 会等待 task 执行完成获取到 Result
// 然后再执行后边的代码,也就是说 执行到这句代码时 由于我们的 Task 中是死循环
// 所以主线程就会被卡死
#endregion
#region 同步执行 Task
// 刚才我们举的例子都是通过多线程异步执行的
// 如果你希望 Task 能够同步执行
// 只需要调用 Task 对象中的 RunSynchronously 方法
// 注意:需要使用 new Task 对象的方式,因为 Run 和 StartNew 在创建时就会启动
Task task = new Task(() =>
{
Thread.Sleep(1000);
print("xxxx");
});
task.RunSynchronously();
// 不 Start 而是 RunSynchronously
#endregion
#region Task 中线程阻塞的方式(任务阻塞)
//1.Wait 方法:等待任务执行完毕,再执行后面的内容
Task t1 = Task.Run(() => {
for (int i = 0; i < 10; i++)
{
print("t1"+ i);
}
});
Task t2 = Task.Run(() => {
for (int i = 0; i < 10; i++)
{
print("t2" + i);
}
});
t1.Wait();
//2.WaitAny 静态方法:传入任务中任意一个任务结束就继续执行
Task.WaitAny(t1, t2);
//3.WaitAll 静态方法:任务列表中所有任务执行结束就继续执行
Task.WhenAll(t1, t2);
print("主线程执行");
#endregion
#region Task 完成后继续其它 Task(任务延续)
//1.WhenAll 静态方法 + ContinueWith 方法:传入任务完毕后再执行某任务
Task.WhenAll(t1,t2).ContinueWith((t) => {
for (int i = 0; i < 10; i++)
{
print("新线程开启" + i);
}
});
Task.Factory.ContinueWhenAll(new Task[] { t1, t2 }, (t) =>
{
for (int i = 0; i < 10; i++)
{
print("新线程开启" + i);
}
});
//2.WhenAny 静态方法 + ContinueWith 方法:传入任务只要有一个执行完毕后再执行某任务
Task.WhenAny(t1, t2).ContinueWith((t) => {
for (int i = 0; i < 10; i++)
{
print("新线程开启" + i);
}
});
Task.Factory.ContinueWhenAny(new Task[] { t1, t2 }, (t) => {
for (int i = 0; i < 10; i++)
{
print("新线程开启" + i);
}
});
#endregion
#region 取消 Task 执行
// 方法一:通过加入 bool 标识 控制线程内死循环的结束
// 方法二:通过 CancellationTokenSource 取消标识源类 来控制
//CancellationTokenSource 对象可以达到延迟取消、取消回调等功能
CancellationTokenSource cts = new CancellationTokenSource();
Task.Run(() => {
int i = 0;
while (!cts.IsCancellationRequested)
{
print(i++);
Thread.Sleep(1000);
}
});
cts.Cancel(); //cts.IsCancellationRequested变成true
cts.CancelAfter(5000);//延迟5秒钟取消
cts.Token.Register(() => {
//取消的时候会调用
});
// 延迟取消
#endregion
}
private void OnDestroy()
{
isRuning = false;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
isRuning = false;
// 获取返回值
print("方式1"+ task.Result);
print("方式2" + task1.Result);
print("方式3" + task2.Result);
}
}
}
异步方法Async和Await关键字
异步方法 async 和 await
async 和 await 一般需要配合 Task 进行使用
async 用于修饰函数、lambda 表达式、匿名函数
await 用于在函数中和 async 配对使用,主要作用是等待某个逻辑结束
此时逻辑会返回函数外部继续执行,直到等待的内容执行结束后,再继续执行异步函数内部逻辑
在一个 async 异步函数中可以有多个 await 等待关键字
使用 async 修饰异步方法
-
在异步方法中使用 await 关键字(不使用编译器会给出警告但不报错),否则异步方法会以同步方式执行
-
异步方法名称建议以 Async 结尾
-
异步方法的返回值只能是 void、Task、Task<>
-
异步方法中不能声明使用 ref 或 out 关键字修饰的变量
使用 await 等待异步内容执行完毕(一般和 Task 配合使用)
遇到 await 关键字时
-
异步方法将被挂起
-
将控制权返回给调用者
-
当 await 修饰内容异步执行结束后,继续通过调用者线程执行后面内容
注意: Unity 中大部分异步方法是不支持异步关键字 async 和 await 的,我们只有使用协程程序进行使用
虽然官方 不支持 但是 存在第三方的工具(插件)可以让 Unity 内部的一些异步加载的方法 支持 异步关键字
https://github.com/svermeulen/Unity3dAsyncAwaitUtil
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
public class lesson1_3 : MonoBehaviour
{
CancellationTokenSource source;
// Start is called before the first frame update
void Start()
{
//TestAsync();
//TimerAsync();
Timer();
print("主线程执行");
}
public async void TestAsync()
{
print("进入异步方法");
await Task.Run(() => {
Thread.Sleep(1000);
});
print("异步方法后面的逻辑");
}
public async void TimerAsync()
{
print("1");
await Task.Delay(1000);
print("2");
await Task.Delay(1000);
print("3");
await Task.Delay(1000);
print("4");
await Task.Delay(1000);
print("5");
await Task.Delay(1000);
print("6");
}
public async void Timer()
{
source = new CancellationTokenSource();
int i = 0;
await Task.Run(() => {
while (!source.IsCancellationRequested)
{
print(i++);
Thread.Sleep(1000);
}
});
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
source.Cancel();
}
}
静态导入
用法:在引用命名空间时,在 using 关键字后面加入 static 关键字
作用:无需指定类型名称即可访问其静态成员和嵌套类型
好处:节约代码量,可以写出更简洁的代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEngine.Mathf;
using static Test;
public class Test
{
public class Test2
{
}
public static void TestTest()
{
Debug.Log("xxxx");
}
}
public class lesson1_4 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Mathf.Max(10, 20);
Max(10, 30);
TestTest();
Test2 test2 = new Test2();
}
// Update is called once per frame
void Update()
{
}
}
异常筛选器
用法:在异常捕获语句块中的 Catch 语句后通过加入 when 关键词来筛选异常
when(表达式)该表达式返回值必须为 bool 值,如果为 true 则执行异常处理,如果为 false,则不执行
作用:用于筛选异常
好处:帮助我们更准确的排查异常
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lesson1_5 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
try
{
//需要捕获异常的语句块
}
catch (Exception e) when (e.Message.Contains("301"))
{
print(e.Message);
}
catch (Exception e) when (e.Message.Contains("404"))
{
print(e.Message);
}
catch (Exception e) when (e.Message.Contains("21"))
{
print(e.Message);
}
catch (Exception e)
{
print(e.Message);
}
}
// Update is called once per frame
void Update()
{
}
}
nameof运算符
用法: nameof (变量、类型、成员) 通过该表达式,可以将他们的名称转为字符串
作用:可以得到变量、类、函数等信息的具体字符串名称
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lesson1_5 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
int i = 10;
print(nameof(i));
print(nameof(List<int>));
print(nameof(List<string>.Add));
print(nameof(Test));
}
public void Test()
{
}
// Update is called once per frame
void Update()
{
}
}
字面值改进
基本概念:在声明数值变量时,为了方便查看数值
可以在数值之间插入_作为分隔符
主要作用:方便数值变量的阅读
int i = 2221_2321_3;
out变量的快捷使用和弃元
用法:不需要再使用带有 out 参数的函数之前,声明对应变量
作用:简化代码,提高开发效率
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lesson1_5 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// 1.以前使用带 out 函数的写法
int a, b;
Calc(out a, out b);
//2.现在的写法
Calc(out int x, out int y);
print(x + "," + y);
//3.结合 var 类型更简便(但是这种写法在存在重载时不能正常使用,必须明确调用的是谁)
Calc(out var x1, out var y1);
print(x1 + "," + y1);
//4.可以使用 _弃元符号 省略不想使用的参数
Calc(out int c,out _);
print(c);
}
public void Calc(out int a, out int b)
{
a = 10;
b = 20;
}
}
ref 修饰临时变量和返回值
基本概念:使用 ref 修饰临时变量和函数返回值,可以让赋值变为引用传递
作用:用于修改数据对象中的某些值类型变量
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.Build;
using UnityEngine;
public struct TestRef
{
public int atk;
public int lev;
public TestRef(int atk, int lev)
{
this.atk = atk;
this.lev = lev;
}
}
public class lesson1_5 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//1. 修饰值类型临时变量
int testI = 100;
ref int testI2 = ref testI;
testI2 = 900;
//2. 获取对象中的参数
TestRef t = new TestRef(10, 10);
ref TestRef t2 = ref t;
t2.lev = 20;
print(t.lev);
ref int temp = ref t.atk;
temp = 60;
print(t.atk);
//3. 函数返回值
int[] numbers = new int[] { 1,2,3,4,5,6,7,8};
ref int number = ref FindNumber(numbers, 5);
number = 11111;
print(numbers[4]);
}
public ref int FindNumber(int[] numbers, int number)
{
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == number)
return ref numbers[i];
}
return ref numbers[0];
}
}
本地函数
基本概念:在函数内部声明一个临时函数
注意:
本地函数只能在声明该函数的函数内部使用
本地函数可以使用声明自己的函数中的变量
作用:方便逻辑的封装
建议:把本地函数写在主要逻辑的后面,方便代码的查看
public int TestTest(int i)
{
i += 10;
Calc();
return i;
void Calc()
{
i += 10;
}
}
抛出表达式
抛出表达式,就是抛出一个错误
一般使用方法就是throw后面new一个异常类
throw new Exception("出错了");
C#常见的自带异常类
//IndexOutOfRangeException: 当一个数组的下标超出范围时运行时引发。
//NullReferenceException: 当一个空对象被引用时运行时引发。
//ArgumentException: 方法的参数是非法的
//ArgumentNullException: 一个空参数传递给方法,该方法不能接受该参数
//ArgumentOutOfRangeException: 参数值超出范围
//SystemException: 其他用户可处理的异常的基本类
//OutOfMemoryException: 内存空间不够
//StackOverflowException 堆栈溢出
//ArithmeticException: 出现算术上溢或者下溢
//ArrayTypeMismatchException: 试图在数组中存储错误类型的对象
//BadImageFormatException: 图形的格式错误
//DivideByZeroException: 除零异常
//DllNotFoundException: 找不到引用的 DLL
//FormatException: 参数格式错误
//InvalidCastException: 使用无效的类
//InvalidOperationException: 方法的调用时间错误
//MethodAccessException: 试图访问私有或者受保护的方法
//MissingMemberException: 访问一个无效版本的 DLL
//NotFiniteNumberException: 对象不是一个有效的成员
//NotSupportedException: 调用的方法在类中没有实现
//InvalidOperationException: 当对方法的调用对对象的当前状态无效时,由某些方法引发。
C# 7中可以在更多表达式中进行错误抛出
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lesson1_6 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// =>符号后面直接throw
Action action = () => throw new Exception();
action();
}
private string jsonStr;
//空合并操作符后用throw
private void InitInfo(string str) => jsonStr = str ?? throw new ArgumentNullException(nameof(str));
//三目运算符后用throw
private string GetInfo(string str, int index)
{
string[] strings = str.Split(",");
return strings.Length > index ? strings[index] : throw new IndexOutOfRangeException(nameof(index));
}
}
元组
基本概念:多个值的集合,相当于是一种快速构建数据结构类的方式
一般在函数存在多返回值时可以使用元组(返回值 1 类型,返回值 2 类型,...)来声明返回值
在函数内部返回具体内容时通过(返回值 1, 返回值 2,...) 进行返回
主要作用:提升开发效率,更方便的处理多返回值等需要用到多个值时的需求
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
public class lesson1_6 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//1.无变量名元组的声明(获取值: Item'N' 作为从左到右依次的参数,N 从 1 开始)
(int, float) yz = (1, 1.1f);
print( yz.Item1);
print( yz.Item2);
//2.有变量名元组的声明
(int i, float b) yz2 = (12, 5.3f);
print(yz2.i);
print(yz2.b);
//3.元组可以进行等于和不等于的判断
//数量相同才比较,类型相同才比较,每一个参数的比较是通过 == 比较 如果都是 true 则认为两个元组相等
if (yz == yz2)
print("相等");
else
print("不相等");
(string,int,float) info = GetInfo();
print(info.Item1 + ";"+info.Item2 +";"+ info.Item3);
var info2 = GetInfo2();
print(info2.s + ";" + info2.i + ";" + info2.f);
//元组的解构赋值
(string myStr, int myInt, float myFloat) = GetInfo();
print(myStr + ";" + myInt + ";" + myFloat);
(string ss , int ii, _) = GetInfo();
print(ss + ";" + ii );
//字典中的键 需要用多个变量来控制
Dictionary<(int i, float f), string> dic = new Dictionary<(int i, float f), string>();
dic.Add((1,1.2f),"sssss");
if (dic.ContainsKey((1, 1.2f)))
{
print("存在");
}
}
private (string, int, float) GetInfo()
{
return ("123",1,4.1f);
}
private (string s, int i, float f) GetInfo2()
{
return ("123", 1, 4.1f);
}
}
模式匹配
基本概念:模式匹配的一种语法元素,可以测试一个值是否满足某种条件,并可以从值中提取信息
在 C#7 中,模式匹配增强了两个现有的语言结构
1.is 表达式,is 表达式可以在右侧写一个模式语法,而不仅仅是一个类型
2.switch 语句中的 case
主要作用:节约代码量,提高编程效率
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lesson1_7 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//1. 常量模式 (is 常量): 用于判断输入值是否等于某个值
object o = 1;
if (o is int) { }
if (o is 1) { }
if (o is null) { }
//2. 类型模式 (is 类型 变量名、case 类型 变量名): 用于判断输入值类型,如果类型相同,将输入值提取出来
// 判断某一个变量是否是某一个类型,如果满足会将该变量存入你申明的变量中
if (o is int i)
{
print(i);
}
switch (o)
{
case int value:
print(value);
break;
case float value:
break;
case double value:
break;
case string value:
break;
case null:
break;
}
//3.var 模式:用于将输入值放入与输入值相同类型的新变量中
// 相当于是将变量装入一个和自己类型一样的变量中
if (o is var v)
{
print(v);
}
}
// Update is called once per frame
void Update()
{
}
}
静态本地函数
静态本地函数就是在本地函数前方加入静态关键字
它的作用就是让本地函数不能够使用访问封闭范围内(也就是上层方法中)的任何变量
作用 让本地函数只能处理逻辑,避免让它通过直接改变上层变量来处理逻辑造成逻辑混乱
public int CalcInfo(int i)
{
bool b = false;
i += 10;
Calc(ref i,ref b);
return i;
static void Calc(ref int i,ref bool b)
{
i += 10;
b = true;
}
}
Using声明
Using 声明就是对 using () 语法的简写
当函数执行完毕时 会调用 对象的 Dispose 方法 释放对象
注意:在使用 using 语法时,声明的对象必须继承 System.IDisposable 接口
因为必须具备 Dispose 方法,所以当声明没有继承该接口的对象时会报错
using StreamWriter s2 = new StreamWriter("");
s2.Write(0);
s2.Flush();
s2.Close();
//上层语句块执行结束时释放
Null合并赋值
空合并赋值是 C#8.0 新加的一个运算符??=
类似复合运算符
左边值??= 右边值
当左侧为空时才会把右侧赋值赋值给变量
注意:由于左侧为空才会讲右侧赋值给变量,所以不为空的变量不会改变
string str = "123";
string str2 = str ?? "234";
print(str2);
str2 ??= "1233";
print(str2);
解构函数Deconstruct
我们可以在自定义类当中声明解构函数
这样我们可以将该自定义类对象利用元组的写法对其进行变量的获取
语法:
在类的内部申明函数 public void Deconstruct (out 变量类型 变量名,out 变量类型 变量名.....)
特点:
一个类中可以有多个 Deconstruct,但是参数数量不能相同
我们可以对该对象利用元组将其具体的变量值 解构出来
相当于把不同的成员变量拆分到不同的临时变量中
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using Unity.VisualScripting.Antlr3.Runtime.Tree;
using UnityEngine;
public class Person
{
public int akt;
public string name;
public int age;
public int id;
public void Deconstruct(out string name, out int akt) => (name, akt) = (this.name,this.akt);
public void Deconstruct(out string name, out int akt, out int age)
{
name = this.name;
akt = this.akt;
age = this.age;
}
public void Deconstruct(out string name, out int akt,out int age,out int id)
{
name = this.name;
akt = this.akt;
age = this.age;
id = this.id;
}
}
public class lessno1_8 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
Person person = new Person();
person.age = 11;
person.akt = 12;
person.id = 13;
person.name = "ssss";
(string name, int atk) = person;
print(name);
print(atk);
(string n,int i1,int i2,int i3) = person;
print(n);
print(i1);
print(i2);
print(i3);
}
}
模式匹配增强功能
switch表达式
switch 表达式是对有返回值的 switch 语句的缩写
用 => 表达式符号代替 case: 组合
用_弃元符号代替 default
它的使用限制,主要是用于 switch 语句当中只有一句代码用于返回值时使用
语法:
函数声明 => 变量 switch
{
常量 => 返回值表达式,
常量 => 返回值表达式,
常量 => 返回值表达式,
....
_ => 返回值表达式,
}
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices.WindowsRuntime;
using UnityEngine;
public enum PosType
{
Top_Left,
Top_Right,
Bottom_Left,
Bottom_Right,
}
public class lesson1_9 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
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)
};
}
属性模式
就是在常量模式的基础上判断对象上各属性
用法:变量 is {属性:值,属性:值}
它可以结合 switch 表达式使用
结合 switch 使用可以通过属性模式判断条件的组合
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices.WindowsRuntime;
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 lesson1_9 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
DiscountInfo info = new DiscountInfo("5折",true);
if (info is { discount: "5折", isDiscount: true })
{
print("信息相同");
}
}
public float GetMoney(DiscountInfo info, float money) => info switch
{
{ discount: "5折", isDiscount: true } => money * 0.5f,
{ discount: "6折", isDiscount: true } => money * 0.6f,
{ discount: "7折", isDiscount: true } => money * 0.7f,
{ discount: "8折", isDiscount: true } => money * 0.8f,
_ => money
};
}
元组模式
通过刚才学习的 属性模式我们可以在 switch 表达式中判断多个变量同时满足再返回什么
但是它必须是一个数据结构类对象,判断其中的变量
而元组模式可以更简单的完成这样的功能,我们不需要声明数据结构类,可以直接利用元组进行判断
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices.WindowsRuntime;
using UnityEngine;
public class lesson1_9 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
int i = 10;
bool b = true;
if ((i, b) is (10, true))
{
print("元组值相同");
}
float f = GetMoney("5折", true, 100);
print(f);
}
public float GetMoney(string discount,bool isDiscount, float money) => (discount,isDiscount) switch
{
("5折",true) => money * 0.5f,
("7折", true) => money * 0.6f,
("8折", true) => money * 0.7f,
("9折", true) => money * 0.8f,
_ => money
};
}
位置模式
如果自定义类中实现了解构函数
那么我们可以直接用对应类对象与元组进行匹配判断
同样我们也可以配合 switch 表达式来处理逻辑
补充:配合 when 关键字进行逻辑处理
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices.WindowsRuntime;
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 dis, out bool isDis) => (dis, isDis) = (this.discount,this.isDiscount);
}
public class lesson1_9 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
DiscountInfo info = new DiscountInfo("5折", true);
if (info is ("5折", true))
{
print("匹配");
}
print(GetMoney(info, 100));
}
public float GetMoney(DiscountInfo info, float money) => info switch
{
("5折", true) => money * 0.5f,
("7折", true) => money * 0.6f,
("8折", true) => money * 0.7f,
("9折", true) => money * 0.8f,
_ => money
};
public float GetMoney2(DiscountInfo info, float money) => info switch
{
(string dis,bool isDis) when dis == "5折" && isDis => money * 0.5f,
(string dis, bool isDis) when dis == "6折" && isDis => money * 0.6f,
(string dis, bool isDis) when dis == "7折" && isDis => money * 0.7f,
(string dis, bool isDis) when dis == "8折" && isDis => money * 0.8f,
_ => money
};
}
日期和时间
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class lesson1_10 : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
#region DateTime
//DateTime 是C#提供给我们处理日期和时间的结构体
//DateTime 对象的默认值和最小值是0001年1月1日00:00:00(午夜)
// 最大值可以是9999年12月31日晚上11:59:59
DateTime dt = new DateTime(2025,10,14,17,28,12);
print(dt.Year + "-" + dt.Month + "-" + dt.Day + "-" + dt.Hour + "-" + dt.Minute + "-" + dt.Second + "-" + dt.Millisecond);
//以格里高利历0000年1月1日00:00:00以来的100纳秒间隔数表示,一般是一个很大的数
print(dt.Ticks);
//一年的第多少天
print(dt.DayOfYear);
//星期几
print(dt.DayOfWeek);
//获取当前日期和时间
DateTime dateTime = DateTime.Now;
print(dateTime);
//返回今天日期
DateTime dateTime1 = DateTime.Today;
print(dateTime1);
//返回当前UTC日期和时间
DateTime dateTime2 = DateTime.UtcNow;
print(dateTime2);
//计算时间
DateTime dateTime3 = dateTime.AddDays(1);
print(dateTime3.Day);
//字符串输出
print(dateTime3.ToString());
print(dateTime3.ToShortTimeString()); //小时 分钟
print(dateTime3.ToShortDateString()); //2001/1/2
print(dateTime3.ToLongTimeString()); //小时 分钟 秒
print(dateTime3.ToLongDateString()); //2001年1月2日
//字符串转DateTime
string str = dateTime.ToString();
DateTime dateTime4;
if(DateTime.TryParse(str,out dateTime4))
{
print(dateTime4);
}
//存储时间
#endregion
#region TimeSpan
//TimeSpan 是C#提供给我们的时间跨度结构体
//用两个DateTime对象相减可以得到该对象
TimeSpan ts = DateTime.Now - new DateTime(1970,1,1);
print(ts.TotalMinutes); //时间差分钟
print(ts.TotalSeconds);
print(ts.TotalDays);
print(ts.TotalHours);
//具体相差时间
print(ts.Days+"-"+ts.Hours+"-"+ts.Minutes+"-"+ts.Seconds);
//初始化代表时间间隔
TimeSpan timeSpan = new TimeSpan(1,0,0,0);
DateTime dateTime5 = DateTime.Now + timeSpan;
print(dateTime5);
//用它相互计算
TimeSpan timeSpan1 =new TimeSpan(0,1,1,1);
TimeSpan timeSpan2 = timeSpan1 + timeSpan;
//自带常量方便用于和ticks进行计算
/*
public const long TicksPerDay = 864000000000;
public const long TicksPerHour = 36000000000;
public const long TicksPerMillisecond = 10000;
public const long TicksPerMinute = 600000000;
public const long TicksPerSecond = 10000000;
*/
print(timeSpan2.Ticks / TimeSpan.TicksPerSecond); //将类型转化为秒 需要转化为什么就除以什么
#endregion
}
// Update is called once per frame
void Update()
{
}
}
更多推荐


所有评论(0)