咖啡丶七

自律给我自由


值类型和引用类型

值类型和引用类型的存储方式

  • 引用类型:引用类型存储在堆中。类型实例化的时候,会在堆中开辟一部分空间存储类的实例。类对象的引用还是存储在栈中。
  • 值类型:值类型总是分配在它声明的地方,做为局部变量时,存储在栈上;类对象的字段时,则跟随此类存储在堆中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Program
{
static void Main(string[] args)
{
// s:对象的引用[存储在栈中]
//new Student()[对象存储在堆中]
Student s = new Student(); // 引用类型

// 局部变量[存储在栈中]
int a = 0; // 值类型
}
}

internal class Student
{
// 类的字段[跟随此类存储在堆中]
public int Age = 0;// 值类型
}

值类型和引用类型的区别

  • 引用类型和值类型都继承自Systerm.Object类。不同之处,值类型则是继承Systerm.Object的子类Systerm.ValueType类,而几乎所有的引用类型都是直接从Systerm.Object继承。
  • 我们在给引用类型的变量赋值的时候,其实只是赋值了对象的引用;而给值类型变量赋值的时候是创建了一个副本(副本不明白?说通俗点,就是克隆了一个变量,即python中的深拷贝)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Program
{
static void Main(string[] args)
{
// 值类型复制
int a = 0;
int a2 = a;
a2 = 10;
Consle.WriteLine(a); // 输出:0

// 引用类型复制
Student s = new Student();
s.Age = 10;
Student s2 = s; // 给引用类型的变量赋值的时候,其实只是赋值了对象的引用
s2.Age = 20;
Console.WriteLine(s.Age); // 输出:20
}
}

internal class Student
{
public int Age = 0;
}

值类型和引用类型的内存分配情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Program
{
static void Main(string[] args)
{
int i = 10; // int类型在栈中占了四个字节(栈中地址:673EED8)
Student s = new Student(); // 一个对象的引用在栈中占四个字节
long j = 20; // long类型栈中占八个字节(栈中地址:673EECC)
}
}

internal class Student
{
public int Age1 = 10;
public int Age2 = 20;
}

  • 栈的结构是后进先出,也就是说:变量j的生命周期在变量s之前结束,变量s的生命周期在变量i之前结束,

  • 栈地址从高往底分配

  • 类型的引用也存储在栈中


堆与栈的详细介绍

搞不明白堆和栈的叫法

堆:在c里面叫堆,在c#里面其实叫托管堆。

栈:就是堆栈,因为和堆一起叫着别扭,就简称栈了。

托管堆

托管堆不同于堆,它是由CLR(公共语言运行库(Common Language Runtime))管理,当堆中满了之后,会自动清理堆中的垃圾。所以,做为.net开发,我们不需要关心内存释放的问题。

搞不清楚内存堆栈与数据结构堆栈

  • 数据结构堆栈:是一种后进先出的数据结构,它是一个概念,图4-1中可以看出,栈是一种后进先出的数据结构。
  • 内存堆栈:存在内存中的两个存储区(堆区,栈区)。
    • 栈区:存放函数的参数、局部变量、返回数据等值,由编译器自动释放
    • 堆区:存放着引用类型的对象,由CLR释放

引用

C#堆与栈


GraphicRaycaster组件

Graphic Raycaster组件一般是和Canvas挂载在同一个物体下面
管理他下面的所有子UI物体的点击响应方式
在一些交互部件没响应的时候可以看下是不是这部分出问题了

如:

  • 激活该组件时,用户从相机发射的摄像是会被挡住的
  • 关闭该组件时,该Canvas下的UI组件如button,dropdown等都无法使用

注意Graphic Raycaster只对UI下的点击交互起作用,而Physics类里面的api不影响UI上面的交互

Ignore Reversed Graphics

这个属性是用来决定当交互部件水平或者垂直翻转到背面对着屏幕(不一定是180度,只要翻转到背面对着屏幕)的时候,是否忽略背面点击,勾上(翻转到背面不能点击) 取消勾选(不管怎么翻转都能点击)

Blocked Objects

这个属性决定了当有物体遮挡在UI前面,并且点击了遮挡部分的时候,是否应该忽略这次点击,
**Three D (3D):**遮挡在本UI前的是带有3DCollider的物体,点击遮挡部分,忽略本UI的响应,(点自己没反应)
**Two D(2D):**遮挡在本UI前的是带有2DCollider的物体,点击遮挡部分,忽略本UI的响应,(点自己没反应)

**None:**不忽略本UI的点击,不管有3D/2D的物体挡住,都响应本UI的点击
**All:**都忽略响应,当UI前的遮挡物体是带有任意Collider组件的,点击遮挡部分的时候,都忽略本UI,(点自己没反应)

Blocking Mask

这个属性一般和Blocked Objects参数一起调节起作用,默认是EveryThing
遮挡的物体如果刚好在勾选的层级下面的话,会构成阻挡点击交互的作用


Canvas Group组件

CanvasGroup可以影响该组UI元素的部分性质,而不需要费力的对该组UI下的每个元素进行逐一得得调整。Canvas Group是同时作用于该组件UI下的全部元素。

参数

Alpha : 该组UI元素的透明度。注:每个UI最终的透明度是由此值和自身的alpha数值相乘得到。

Interactable : 是否需要交互(勾选的则是可交互),同时作用于该组全部UI元素。

Blcok Raycasts : 是否可以接收图形射线的检测(勾选则接受检测)。注:不适用于Physics.Raycast.。

Ignore Parent Group : 是否需要忽略父级对象中的CanvasGroup的设置。(勾选则忽略)

应用场景

  • 在窗口的GameObject上添加一个CanvasGroup,通过控制它的Alpha值来淡入淡出整个窗口

  • 通过给父级GameObject上添加一个CanvasGroup并设置它的Interactable值为false来设置一套没有交互(灰色)的控制

  • 通过将元素或元素的一个父级添加Canvas Group并设置BlockRaycasts值为false来制作一个或多个不阻止鼠标事件的UI元素

CanvasGroup的Alpha与SetActive()方法比较:

  • CanvasGroup的Alpha与SetActive()两者之间的性能区别不大

  • CanvasGroup的Alpha由0设为1的时候,并不会让自己活着的子节点中脚本执行Awake()方法,而SetActive(true)则会执行Awake()方法

  • CanvasGroup的Alpha设为0和SetActive(false)的时候,同样不会调用drawcall

小实例(一闪一暗)代码:

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour {

private float alpha0 = 0.0f;

private float alphaSpeed = 2.0f;
bool isShow;

private CanvasGroup cg;

void Start () {
cg = this.transform.GetComponent<CanvasGroup>();
}

void Update ()
{
if (!isShow)
{
cg.alpha = Mathf.Lerp(cg.alpha, alpha0, alphaSpeed * Time.deltaTime);
if (Mathf.Abs(alpha0 - cg.alpha) <= 0.01)
{
isShow = true;
cg.alpha = alpha0;
alpha0 = 1;
}
}
else
{
//*0.5是因为从隐藏当显示感觉很快
cg.alpha = Mathf.Lerp(cg.alpha, alpha0, alphaSpeed * Time.deltaTime * 0.5f);
if (Mathf.Abs(alpha0 - cg.alpha) <= 0.01)
{
isShow = false;
cg.alpha = alpha0;
alpha0 = 0;
}
}
}

}

判断鼠标是否在UI上

1
2
3
# true:在UI上,false:不在UI上
# 受GraphicRaycaster组件影响,鼠标在未激活GraphicRaycaster的canvas上时返回false
bool isOn = UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject()

转载:
unity Graphic Raycaster 作用详解
Unity中CanvasGroup组件

使用

命令 解释
top -d 1 1秒跟新一次
top -p {PID} 指定特定的pid进程号进行观察

命令

在top界面还可以输入命令(需要区分大小写)

命令 解释
d 更改刷新时间间隔
P CPU使用率降序排序
M 内存使用率降序排序
T 进程使用时间降序排序
k 杀进程
m 切换到内存模式
1 查看每个CPU的详细信息

参数含义

第一行

top - 11:48:58 up 3:08, 1 user, load average: 0.40, 0.32, 0.41

内容 含义
11:48:58 当前时间
up 3:08 系统运行时间 格式为时:分
1 user 当前登入用户数
load average: 0.40, 0.32, 0.41 系统负载,即任务队列的平均长度。

load average
三个数值分别为 1分钟、5分钟、15分钟前到现在的平均值
如果这个数除以逻辑CPU的数量,结果高于5的时候就表明系统在超负荷运转了

第二行

Tasks: 453 total, 2 running, 451 sleeping, 0 stopped, 0 zombie

内容 含义
Tasks: 453 total 总进程数
2 running 正在运行的进程数(即正在使用CPU执行任务)
451 sleeping 休眠的进程数(即处于非执行状态,等待被唤醒)
0 stopped 停止的进程数(即处于停止状态)
0 zombie 僵尸进程数
  • sleep休眠:表示当前没有运行但处于等待状态的进程数量。这些进程暂时没有分配到 CPU 时间,它们处于休眠状态,等待某个事件的发生,如等待 I/O 完成、等待信号、或者等待其他资源。休眠状态的进程不会占用 CPU 时间,系统调度器会将 CPU 时间分配给其他需要执行的进程。
  • stopped暂停:表示当前被暂停(停止)的进程数量。这些进程可能是由用户发送 SIGSTOP 信号而暂停的。停止的进程不会占用 CPU 时间。
  • 僵尸进程是已经终止(terminated)但其父进程尚未等待(wait)其终止状态的进程。当一个进程结束时,它的退出状态(exit status)会保留在系统中,直到其父进程通过 wait() 系统调用来获取。如果父进程没有等待子进程的终止状态,那么子进程就会变成僵尸进程。僵尸进程占用了进程表中的资源,因此应该及时被清理,通常是通过父进程调用 wait()waitpid() 来回收。

第三行

%Cpu(s): 7.0 us, 0.2 sy, 0.3 ni, 92.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

内容 含义
7.0us 用户空间(User space)占用CPU百分比,即用户进程占用的CPU时间百分比
0.2sy 内核空间(System space)占用CPU百分比,即内核进程占用的CPU时间百分比
0.3ni 表示调整过优先级的用户态进程的 CPU 使用百分比。ni 表示 “nice” 值,即用户通过 nice 命令调整的进程优先级
92.5 id 空闲CPU百分比
0.0 wa 等待输入输出的CPU时间百分比
0.0 hi 硬中断(Hardware IRQ)占用CPU的百分比
0.0 si 软中断(Software Interrupts)占用CPU的百分比
0.0 st 被虚拟化环境中其他虚拟机”偷走”(Steal)的 CPU 时间百分比

第四、五行

MiB Mem : 16000.5 total, 5664.6 free, 8620.5 used, 1715.4 buff/cache

MiB Swap: 0.0 total, 0.0 free, 0.0 used. 7069.0 avail Mem

内容 含义
MiB Mem
16000.5 total 物理内存总量
5664.6 free 空闲内存总量
8620.5 used 使用的物理内存总量
1715.4 buff/cache 用作内核缓存的内存量
MiB Swap
0.0 total 交换区总量
0.0 free 空闲交换区总量
0.0 used 使用的交换区总量
7069.0 avail Mem 代表可用于进程下一次分配的物理内存数量
617312 cached Mem 缓冲的交换区总量

上述最后提到的缓冲的交换区总量,这里解释一下,所谓缓冲的交换区总量,即内存中的内容被换出到交换区,而后又被换入到内存,但使用过的交换区尚未被覆盖,该数值即为这些内容已存在于内存中的交换区的大小。相应的内存再次被换出时可不必再对交换区写入。

进程信息

列明 含义
PID 进程id
PPID 父进程id
RUSER Real user name
UID 进程所有者的用户id
USER 进程所有者的用户名
GROUP 进程所有者的组名
TTY 启动进程的终端名。不是从终端启动的进程则显示为 ?
PR 优先级
NI nice值。负值表示高优先级,正值表示低优先级
P 最后使用的CPU,仅在多CPU环境下有意义
%CPU 上次更新到现在的CPU时间占用百分比
TIME 进程使用的CPU时间总计,单位秒
TIME+ 进程使用的CPU时间总计,单位1/100秒
%MEM 进程使用的物理内存百分比
VIRT 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
SWAP 进程使用的虚拟内存中,被换出的大小,单位kb
RES 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
CODE 可执行代码占用的物理内存大小,单位kb
DATA 可执行代码以外的部分(数据段+栈)占用的物理内存大小,单位kb
SHR 共享内存大小,单位kb
nFLT 页面错误次数
nDRT 最后一次写入到现在,被修改过的页面数
S 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
COMMAND 命令名/命令行
WCHAN 若该进程在睡眠,则显示睡眠中的系统函数名
Flags 任务标志

前言

配置环境变量的位置在/etc/profile文件中(系统级别的配置文件)
~/.bashrc是用户级别的配置文件

在修改/etc/profile之后需要执行source /etc/profile命令使修改内容生效
~/.bashrc是该用户每次登入时会自动运行。以root用户进入时会执行/root/.bashrc配置文件,以steam用户进入会执行/home/steam/.bashrc配置文件

两个文件可以实现完美的搭配:

  1. 修改/etc/profile配置文件实现自己的需求,如export PS1='[\t \u@\h \w]\$ '
  2. ~/.bashrc文件末尾添加source /etc/profile

这样在每次用户进入后都会执行一遍source /etc/profile命令,使/etc/profile配置文件生效

创建快捷方式

/usr/bin/路径下创建一个pbulic快捷方式,使其指向/home/hexo/public

1
ln -sf /home/hexo/public /usr/bin/public
  • ln:创建链接命令
  • -s:表示创建软链接
  • -f:表示在目标文件存在时强制删除并重新创建

public可以是文件夹,也可以是文本文件,也可以是执行文件(linux一个路径下不允许同时存在同名文件,同名的文件夹和文本文件也不行,所以不用担心索引错)
不管是什么,在访问/usr/bin/public时等同于访问/home/hexo/public

  • 文件夹:cd /usr/bin/public等同于cd /home/hexo/public
  • 文本文件:vim /usr/bin/public等同于vim /home/hexo/public
  • 执行文件:/usr/bin/public等同于/home/hexo/public

执行文件最好放在/usr/bin/路径下,该路径下的执行文件是全局可用的
如,使用ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx创建了nginx的快捷方式,则在任意路径下执行nginx都等同于执行/usr/local/nginx/sbin/nginx

使用systemctl让服务器在后台运行

需要在/etc/systemd/system/路径下创建后缀为.service服务器配置文件,如vim /etc/systemd/system/pal.service

文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Unit]
# 服务器简介
Descripteion=pal server
# 前置service,都在/etc/systemd/system/下,network.target是连接上网络
After=network.target XXX.service

[Service]
# 指定运行的sh
ExecStart=/path/to/start/PalServer.sh
# 停止和重新加载服务时执行的命令
ExecStop=/path/to/stop/command
ExecReload=/path/to/reload/command
# 指定服务器运行的用户和组
User=yourusername
Group=yourgroupname
# 定义服务器在退出后是否自动重启
Restart=always

# 默认格式
[Install]
WantedBy=multi-user.target
systemctl常用命令 含义
sudo systemctl daemon-reload 重载服务,每次修改后需要执行
sudo systemctl start pal 启动服务,可不用后缀直接使用pal
sudo systemctl stop pal 停止服务
sudo systemctl restart pal 重启服务
sudo systemctl status pal 查看状态
sudo systemctl enable pal 设置开机启动
sudo systemctl disenable pal 关闭开机启动
sudo journalctl -u pal 查看服务日志
sudo journalctl -f 持续输出所有后台服务器日志

配置shell命令提示符

PS1代表提示符的变量,可以在shell中输入echo $PS1查看当前PS1的值

个人喜欢的一个配置export PS1='[\t \u@\h \w]\$ '

缩写 含义
\d 代表日期,格式为weekday month date
\H 完整的主机名称
\h 仅主机的第一个名字
\t 显示24小时格式的时间,HHMMSS
\T 显示12小时格式的时间
\A 显示24小时格式的时间,HHMM
\u 当前账户的账号名称
\v BASH的版本信息
\w 完整的工作目录名称,家目录会以~显示
\W 利用basename去的工作目录名称,所以只会列出最后一个目录
# 下达的第几个命令
$ 提示字符,如果是root,提示符为#,普通用户则为$


在unity3D中经常用线性插值函数Lerp()来在两者之间插值,两者之间可以是两个材质之间、两个向量之间、两个浮点数之间、两个颜色之间。

为了更好的理解线性插值的概念,我们先讨论一下浮点数的线性插值

浮点数的线性插值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void Start()
{
Debug.Log(Mathf.Lerp(0, 100, 0).ToString());
Debug.Log(Mathf.Lerp(0, 100, 0.1f).ToString());
Debug.Log(Mathf.Lerp(0, 100, 0.2f).ToString());
Debug.Log(Mathf.Lerp(0, 100, 0.3f).ToString());
Debug.Log(Mathf.Lerp(0, 100, 0.4f).ToString());
Debug.Log(Mathf.Lerp(0, 100, 0.5f).ToString());
Debug.Log(Mathf.Lerp(0, 100, 0.6f).ToString());
Debug.Log(Mathf.Lerp(0, 100, 0.7f).ToString());
Debug.Log(Mathf.Lerp(0, 100, 0.8f).ToString());
Debug.Log(Mathf.Lerp(0, 100, 0.9f).ToString());
Debug.Log(Mathf.Lerp(0, 100, 1).ToString());
}

// 结果为
// 0
// 10
// 20
// ....
// 90
// 100

可以看出输出的结果最终取决于第三个参数
如果将第一个参数改为100,第二个参数改为110,第三个参数不变,那么结果为100,101,102,103,104,105,106,107,108,109,110。

向量的线性插值

1
2
3
4
void Update()
{
transform.position = Vector3.Lerp(A, B, Time.time);
}

该物体会慢慢的移动到从A移动到B

现有两个点a, b

假设Y值都相等

  • a到b的向量:var direction = a - b;
  • 以a为中心,世界坐标为方向,b在a的什么弧度上:var angleRag = math.atan2(direction.z, direction.x);
  • 什么角度上:var angleDeg = math.degrees(angleRag)

向量之间的计算

点乘(内积)运算

a 与 b 的点乘(也称内积)公式如下

$$
\vec{a}\vec{b}=|\vec{a}||\vec{b}|cosθ (0°<θ<180°)
$$
若a=(ax, ay, az),b=(bx, by, bz),则
$$
\vec{a}
\vec{b}=a_xb_x+a_yb_y+a_z*b_z
$$
在unity中,提供了Vector3.Dot()方法计算两个向量的点乘

1
2
3
4
5
6
7
8
9
10

Vector3 vectorA = new Vector3(1, 2, 3);
Vector3 vectorB = new Vector3(4, 5, 6);

float dotProduct = Vector3.Dot(vectorA, vectorB);

// 结果输出: 32,32>0,即向量a,b的夹角为锐角
// dotProduct > 0,为锐角
// dotProduct = 0, 为直角
// dotProduct < 0, 为钝角

点乘的结果是|a||b|cosθ

  • 如果只用判断两个向量夹角的关系,则只用判断dotProduct的值就好了
  • 如果需要得到cosθ,需要除以|a||b|

叉乘(外积)运算

a 与 b 的叉乘(也称外积、向量积、叉积)公式如下
$$
|\vec{c}|=|\vec{a} \times \vec{b}|=|\vec{a}|*|\vec{b}|*sinθ
$$

若a=(a_x, a_y, a_z),b=(b_x, b_y, b_z),i,j,k分别为x,y,z轴的单位向量,则
$$
\vec{a}×\vec{b}=(a_yb_z-a_zb_y)\vec{i}+(a_zb_x-a_xb_z)\vec{j}+(a_xb_y-a_yb_x)\vec{k}
$$
在unity中,提供了Vector3.Cross()方法计算两个向量的叉乘

1
2
3
4
5
6
Vector3 vectorA = new Vector3(1, 2, 3);
Vector3 vectorB = new Vector3(4, 5, 6);

Vector3 crossProduct = Vector3.Cross(vectorA, vectorB);

// 结果输出: (3, -6, 3)

叉乘的结果是是一个新的向量c,c垂直与a和b所在平面,且方向由右手定则确定(当右手的四指从a以不超过180度的转角转向b时,竖起的大拇指指向是c的方向)。但Unity是左手坐标系,所以这里应该是左手定则

1
2
// right是(1, 0, 0)    forward是(0, 0, 1)
Vector3.Cross(Vector3.right, Vector3.forward) = (0, -1, 0)

数乘运算

数乘是将向量的每个坐标值乘以该数值
$$
k \vec{v} = (k \times v_x, k \times v_y, k \times v_z)
$$


向量的概念

向量的模

返回向量的长度

1
float length = vector.magnitude;

向量的平方长度

返回向量的平方长度,通常用于比较向量大小而无需进行开方运算,从而提高效率

1
float sqrMagnitude = vector.sqrMagnitude;

单位向量

返回向量的单位向量,即长度为1但方向相同的向量

1
Vector3 normalizedVector = vector.normalized;

线性插值

在两个向量之间进行线性插值

1
Vector3 interpolatedVector = Vector3.Lerp(startVector, endVector, t);

两点之间的距离

返回两点之间的距离

1
float distance = Vector3.Distance(vectorA, vectorB);

向量之间的夹角

返回两个向量之间的夹角

1
float angle = Vector3.Angle(vectorA, vectorB);

投影向量

返回向量在另一个向量上的投影向量,一般是计算在坐标轴上的投影向量

1
Vector3 projectionVector = Vector3.Project(vectorToProject, ontoVector);

示例:

1
2
Vector3 B = new Vector3(2, 2, 2);
Vector3 result = Vector3.Project(B, Vector3.forward); // 结果为 OP(0,0,2)

上图中是边长为2的正方形,OP是B在Z轴上的投影
B是B减去OP的向量,B(2,2,0) (向量本身减去此投影向量就为在平面上的向量)

反射向量

返回在另一个向量上的反射向量

1
Vector3 reflectionVector = Vector3.Reflect(incidentVector, normal);

浅拷贝和深拷贝的区别

  • 浅拷贝只是拷贝了原对象的引用,而不是对象本身。因此,浅拷贝后的对象与原对象共享内存空间,即修改其中一个对象的值会影响到另一个对象的值
  • 深拷贝则是完全拷贝了原对象及其引用的对象,因此,深拷贝后的对象与原对象不共享内存空间,即修改其中一个对象的值不会影响到另一个对象的值

在C#中深拷贝的用法

反射

输入的对象可以和输出的对象不一样。如果输出的对象拥有与输入的对象相同的属性,则会把该属性的值赋给输出对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static TOut TransReflection<TIn, TOut>(TIn tIn)
{
// 创建一个新的 TOut 实例
TOut tOut = Activator.CreateInstance<TOut>();
// 获取到 tIn 的类型
var tInType = tIn.GetType();
// 遍历 TOut 的属性
foreach (var itemOut in tOut.GetType().GetProperties())
{
// 获取到 tIn 的属性
var itemIn = tInType.GetProperty(itemOut.Name);

if (itemIn != null)
{
// 将 tIn 的属性赋给 tOut 的对应属性
itemOut.SetValue(tOut, itemIn.GetValue(tIn));
}
}
return tOut;
}

第二种反射反射深拷贝函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static T DeepCopy<T>(T obj)
{
if (obj == null)
{
return obj;
}
var type = obj.GetType();
if (obj is string || type.IsValueType)
{
return obj;
}

var result = Activator.CreateInstance(type);
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
foreach (var field in fields)
{
field.SetValue(result, field.GetValue(obj));
}
return (T)result;
}

完整代码如下:

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
using System;

namespace AsyncTest
{
class Program
{
static void Main(string[] args)
{
Player A = new Player("A", 10);
//Player B = A; // 浅拷贝
Player B = TransReflection<Player, Player>(A); // 深拷贝
B.Age = 20;
Console.WriteLine($"{A.Name} {A.Age}");
Console.WriteLine($"{B.Name} {B.Age}");
Console.ReadKey();
}
private static TOut TransReflection<TIn, TOut>(TIn tIn)
{
// 创建一个新的 TOut 实例
TOut tOut = Activator.CreateInstance<TOut>();
// 获取到 tIn 的类型
var tInType = tIn.GetType();
// 遍历 TOut 的属性
foreach (var itemOut in tOut.GetType().GetProperties())
{
// 获取到 tIn 的属性
var itemIn = tInType.GetProperty(itemOut.Name);

if (itemIn != null)
{
// 将 tIn 的属性赋给 tOut 的对应属性
itemOut.SetValue(tOut, itemIn.GetValue(tIn));
}
}
return tOut;
}
}
public class Player
{
public string Name { get; set; }
public int Age { get; set; }
public Player() { }
public Player(string name, int age)
{
this.Name = name;
this.Age = age;
}
}
}

静态转换(Static Cast)

静态转换是将一种数据类型的值强制转换为另一种数据类型的值。

静态转换通常用于比较类型相似的对象之间的转换,例如将 int 类型转换为 float 类型。

静态转换不进行任何运行时类型检查,因此可能会导致运行时错误。

1
2
int i = 10;
float f = static_cast<float>(i); // 静态将int类型转换为float类型

动态转换(Dynamic Cast)

动态转换通常用于将一个基类指针或引用转换为派生类指针或引用。动态转换在运行时进行类型检查,如果不能进行转换则返回空指针或引发异常。

1
2
3
4
class Base {};
class Derived : public Base {};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base); // 将基类指针转换为派生类指针

常量转换(Const Cast)

常量转换用于将 const 类型的对象转换为非 const 类型的对象。

常量转换只能用于转换掉 const 属性,不能改变对象的类型。

1
2
const int i = 10;
int& r = const_cast<int&>(i); // 常量转换,将const int转换为int

拓展:const定义的常量表示只读,不能更改。


重新解释转换(Reinterpret Cast)

重新解释转换将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换。

重新解释转换不进行任何类型检查,因此可能会导致未定义的行为。

1
2
int i = 10;
float f = reinterpret_cast<float&>(i); // 重新解释将int类型转换为float类型