当前位置: 首页 > news >正文

理解C#中对象的浅拷贝和深拷贝

本文章主要介绍C#中对象的拷贝,其中包括浅拷贝和深拷贝,以及浅拷贝和深拷贝的实现方式,不同的实现方式之间的性能对比。

 

1、浅拷贝和深拷贝

浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。如果改变目标对象中引用型字段的值他将反映在原是对象中,也就是说原始对象中对应的字段也会发生变化。深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一个新的和原是对象中对应字段相同(内容相同)的字段,也就是说这个引用和原是对象的引用是不同的,我们在改变新对象中的这个字段的时候是不会影响到原始对象中对应字段的内容。

2、浅拷贝实现方式

1)new对象赋值

[Serializable]
class Employee
{public string ID { get; set; }public int Age { get; set; }public Department DepartmentName { get; set; }
}
[Serializable]
class Department
{public string DepartmentName { get; set; }public Department(string value){DepartmentName = value;}public override string ToString(){return DepartmentName.ToString();}
}
Employee emp1 = new Employee()
{ID = "cjavapy",Age = 20,DepartmentName = new Department("develop")
}
;
Employee emp2=new Employee()
{ID=emp1.ID,Age=emp1.Age,DepartmentName=emp1.DepartmentName
};

 2)实现ICloneable接口

class Employee : ICloneable
{public string ID { get; set; }public int Age { get; set; }public Department DepartmentName { get; set; }//实现ICloneable接口的Clone方法public object Clone(){return this.MemberwiseClone();//浅拷贝}
}
class Department
{public string DepartmentName { get; set; }public Department(string value){DepartmentName = value;}public override string ToString(){return DepartmentName.ToString();}
}

 浅拷贝:

Employee emp1 = new Employee()
{ID = "cjavapy",Age = 20,DepartmentName = new Department("develop")
};
Employee emp2 = emp1.Clone() as Employee;//浅拷贝

 

3、深拷贝实现方式

1)二进制序列化

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using Newtonsoft.Json;namespace ConsoleApplication
{public class Utils{public static T BinaryClone<T>(T source){if (!typeof(T).IsSerializable){throw new ArgumentException("需要添加[Serializable]标签", "source");}if (Object.ReferenceEquals(source, null)){return default(T);}IFormatter formatter = new BinaryFormatter();Stream stream = new MemoryStream();using (stream){formatter.Serialize(stream, source);stream.Seek(0, SeekOrigin.Begin);return (T)formatter.Deserialize(stream);}}}}

 2)JSON序列化

using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using Newtonsoft.Json;namespace ConsoleApplication
{public class Utils{/// /// 序列化反序列化方式/// /// /// public static TOut JsonClone<TIn,TOut>(TIn tIn){return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn));}}}

 3)Reflection反射

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApplication
{public class Utils{/// /// 反射实现深拷贝/// /// 传入TIn对象返回TOut对象/// public static TOut ReflectionClone<TIn, TOut>(TIn tIn){TOut tOut = Activator.CreateInstance<TOut>();foreach (var itemOut in tOut.GetType().GetProperties()){var propIn = tIn.GetType().GetProperty(itemOut.Name);itemOut.SetValue(tOut, propIn.GetValue(tIn));}foreach (var itemOut in tOut.GetType().GetFields()){var fieldIn = tIn.GetType().GetField(itemOut.Name);itemOut.SetValue(tOut, fieldIn.GetValue(tIn));}return tOut;}/// <summary>/// 传入List<TIn>,返回List<TOut>/// </summary>/// <typeparam name="TIn"></typeparam>/// <typeparam name="TOut"></typeparam>/// <param name="tInList"></param>/// <returns></returns>public static List<TOut> ReflectionCloneList<TIn, TOut>(List<TIn> tInList){List<TOut> result = new List<TOut>();foreach (var tIn in tInList){TOut tOut = Activator.CreateInstance<TOut>();foreach (var itemOut in tOut.GetType().GetProperties()){var propIn = tIn.GetType().GetProperty(itemOut.Name);itemOut.SetValue(tOut, propIn.GetValue(tIn));}foreach (var itemOut in tOut.GetType().GetFields()){var fieldIn = tIn.GetType().GetField(itemOut.Name);itemOut.SetValue(tOut, fieldIn.GetValue(tIn));}result.Add(tOut);}return result;}}public class ContactPerson{public string Name { get; set; }public string MobileNum { get; set; }}class Program{static void Main(string[] args){var persons = new List<ContactPerson>{new ContactPerson { Name= "C", MobileNum = "13756863001"},new ContactPerson { Name = "C#", MobileNum = "13756863002"},new ContactPerson { Name = "Java", MobileNum = "13756863003"}};var result = Utils.ReflectionCloneList<ContactPerson, ContactPerson>(persons);foreach(var p in result)Console.WriteLine("姓名: {0} 号码为: {1}", p.Name, p.MobileNum);Console.Read();}}
}

 4)XML序列化

using System.IO;
using System.Xml.Serialization;namespace ConsoleApplication
{public class Utils{public static T DeserializeXML<T>(string xmlData) where T : new(){if (string.IsNullOrEmpty(xmlData))return default(T);TextReader tr = new StringReader(xmlData);T DocItms = new T();XmlSerializer xms = new XmlSerializer(DocItms.GetType());DocItms = (T)xms.Deserialize(tr);return DocItms == null ? default(T) : DocItms;}}
}

 5)表达式目录树

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApplication
{/// <summary>/// 生成表达式目录树  泛型缓存/// </summary>/// <typeparam name="TIn"></typeparam>/// <typeparam name="TOut"></typeparam>public class ExpressionGenericMapper<TIn, TOut>//`2{private static Func<TIn, TOut> _FUNC = null;static ExpressionGenericMapper(){ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");List<MemberBinding> memberBindingList = new List<MemberBinding>();foreach (var item in typeof(TOut).GetProperties()){MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));MemberBinding memberBinding = Expression.Bind(item, property);memberBindingList.Add(memberBinding);}foreach (var item in typeof(TOut).GetFields()){MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));MemberBinding memberBinding = Expression.Bind(item, property);memberBindingList.Add(memberBinding);}MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]{parameterExpression});_FUNC = lambda.Compile();//}public static TOut Trans(TIn t){return _FUNC(t);}}
}

 

4、拷贝方式性能对比

对比代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;namespace ConsoleApplication
{class Program{static void Main(string[] args){Employee emp1 = new Employee(){ID = "cjavapy",Age = 20,DepartmentName = new Department("develop")};long common = 0;long expression = 0;long json = 0;long xml = 0;long binary = 0;long reflection = 0;{Stopwatch watch = new Stopwatch();watch.Start();for (int i = 0; i < 1000000; i++){Employee copy = BinaryClone<Employee>(emp1);}watch.Stop();binary = watch.ElapsedMilliseconds;}{Stopwatch watch = new Stopwatch();watch.Start();for (int i = 0; i < 1000000; i++){Employee copy = ReflectionClone<Employee, Employee>(emp1);}watch.Stop();reflection = watch.ElapsedMilliseconds;}{Stopwatch watch = new Stopwatch();watch.Start();for (int i = 0; i < 1000000; i++){Employee copy = DeserializeXML<Employee>(SerializeXML<Employee>(emp1));}watch.Stop();xml = watch.ElapsedMilliseconds;}{Stopwatch watch = new Stopwatch();watch.Start();for (int i = 0; i < 1000000; i++){Employee copy = JsonClone<Employee, Employee>(emp1);}watch.Stop();json = watch.ElapsedMilliseconds;}{Stopwatch watch = new Stopwatch();watch.Start();for (int i = 0; i < 1000000; i++){Employee copy = ExpressionGeneric<Employee, Employee>.Clone(emp1);}watch.Stop();expression = watch.ElapsedMilliseconds;}Console.WriteLine($"binary = { binary} ms");Console.WriteLine($"reflection = { reflection} ms");Console.WriteLine($"serialize = { xml} ms");Console.WriteLine($"json = { json} ms");Console.WriteLine($"generic = { expression} ms");Console.ReadKey();}public static T BinaryClone<T>(T source){if (!typeof(T).IsSerializable){throw new ArgumentException("需要添加[Serializable]标签", "source");}if (Object.ReferenceEquals(source, null)){return default(T);}IFormatter formatter = new BinaryFormatter();Stream stream = new MemoryStream();using (stream){formatter.Serialize(stream, source);stream.Seek(0, SeekOrigin.Begin);return (T)formatter.Deserialize(stream);}}/// <summary>/// 反射/// </summary>/// <typeparam name="TIn"></typeparam>/// <typeparam name="TOut"></typeparam>/// <param name="tIn"></param>/// <returns></returns>public static TOut ReflectionClone<TIn, TOut>(TIn tIn){TOut tOut = Activator.CreateInstance<TOut>();foreach (var itemOut in tOut.GetType().GetProperties()){var propIn = tIn.GetType().GetProperty(itemOut.Name);itemOut.SetValue(tOut, propIn.GetValue(tIn));}foreach (var itemOut in tOut.GetType().GetFields()){var fieldIn = tIn.GetType().GetField(itemOut.Name);itemOut.SetValue(tOut, fieldIn.GetValue(tIn));}return tOut;}public static TOut JsonClone<TIn, TOut>(TIn tIn){return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn));}public static string SerializeXML<T>(T t){using (StringWriter sw = new StringWriter()){XmlSerializer xz = new XmlSerializer(t.GetType());xz.Serialize(sw, t);return sw.ToString();}}public static T DeserializeXML<T>(string xmlData) where T : new(){if (string.IsNullOrEmpty(xmlData))return default(T);TextReader tr = new StringReader(xmlData);T DocItms = new T();XmlSerializer xms = new XmlSerializer(DocItms.GetType());DocItms = (T)xms.Deserialize(tr);return DocItms == null ? default(T) : DocItms;}}[Serializable]public class Employee : ICloneable{public string ID { get; set; }public int Age { get; set; }public Department DepartmentName { get; set; }//实现ICloneable接口的Clone方法public object Clone(){return this.MemberwiseClone();//浅拷贝}}[Serializable]public class Department{public string DepartmentName { get; set; }public Department(){}public Department(string value){DepartmentName = value;}public override string ToString(){return DepartmentName.ToString();}}/// <typeparam name="TIn"></typeparam>/// <typeparam name="TOut"></typeparam>public class ExpressionGeneric<TIn, TOut>//Mapper`2{private static Func<TIn, TOut> _FUNC = null;static ExpressionGeneric(){ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");List<MemberBinding> memberBindingList = new List<MemberBinding>();foreach (var item in typeof(TOut).GetProperties()){MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));MemberBinding memberBinding = Expression.Bind(item, property);memberBindingList.Add(memberBinding);}foreach (var item in typeof(TOut).GetFields()){MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));MemberBinding memberBinding = Expression.Bind(item, property);memberBindingList.Add(memberBinding);}MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]{parameterExpression});_FUNC = lambda.Compile();}public static TOut Clone(TIn t){return _FUNC(t);}}
}

 

相关文章:

理解C#中对象的浅拷贝和深拷贝

本文章主要介绍C#中对象的拷贝&#xff0c;其中包括浅拷贝和深拷贝&#xff0c;以及浅拷贝和深拷贝的实现方式&#xff0c;不同的实现方式之间的性能对比。 1、浅拷贝和深拷贝 浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中&#xff0c;而对象中的引用型字段则指复制它…...

js 生成随机数(含随机颜色)

生成 0-1 之间的随机数 Math.random()生成 0-x 之间的随机整数&#xff1a; Math.round(Math.random()*x)生成 min-max 之间的随机整数&#xff1a; Math.round(Math.random()*(max-min)min)生成N位随机数 /*** 函数--生成N位随机数* param {*} N 数字的长度*/ function random…...

【axios】axios的基本使用

一、 Axios简介 1、 Axios是什么&#xff1f; Axios是一个基于promise的HTTP库&#xff0c;类似于jQuery的ajax&#xff0c;用于http请求。可以应用于浏览器端和node.js&#xff0c;既可以用于客户端&#xff0c;也可以用于node.js编写的服务端。 2.、Axios特性 支持Promis…...

React 在非组件环境切换路由

我的react-router-dom版本是6.16.0。之前在react中是这样配置路由的 App.jsx import ReactDOM from react-dom/client; import { HashRouter, Route, Routes } from react-router-dom;const root ReactDOM.createRoot(document.getElementById("app")); root.rend…...

Oracle高速批量速插入数据解决方案

最近做短信群发项目有一个需求,需要客户大批量(十万级)导入数据. 开始是用insert单条数据,10万条数据要20分钟 后来发现可以用insert all 一条sql一次导入500条记录,这样10万条数据只用了1.5分钟,导入速度提高了近来20倍 下面就使用insert all的心得体会记录如下. 使用方法: i…...

基于单片机嵌入式的智能交通信号灯管理系统的设计与实现

项目介绍 有目共睹电子设备已经席卷了整个人类生活&#xff0c;他们不断改善着人们的起居住行&#xff0c;这也就促进了嵌入式人工智能的快速发展。 本课设模拟系统分为软硬件两部分组成。硬件部分是由两位8段数码管和LED灯构成的显示系统和控制电路等组成&#xff0c;能较好的…...

在全新ubuntu上用gpu训练paddleocr模型遇到的坑与解决办法

目录 一. 我的ubuntu版本![在这里插入图片描述](https://img-blog.csdnimg.cn/297945917309494ab03b50764e6fb775.png)二.首先拉取paddleocr源代码三.下载模型四.训练前的准备1.在源代码文件夹里创造一个自己放东西的文件2.准备数据2.1数据标注2.2数据划分 3.改写yml配置文件4.…...

React之服务端渲染

一、是什么 在SSR中 (opens new window)&#xff0c;我们了解到Server-Side Rendering &#xff0c;简称SSR&#xff0c;意为服务端渲染 指由服务侧完成页面的 HTML 结构拼接的页面处理技术&#xff0c;发送到浏览器&#xff0c;然后为其绑定状态与事件&#xff0c;成为完全可…...

jetson nano刷机更新Jetpack

只是记录个人在使用英伟达jetson Nano的经历,由于头一次尝试,所以特此记录需要的问题和经验。 一,英伟达刷机教程(jetson nano 版本) 本次我是直接刷机到TF卡,然后TF卡作为启动盘进行启动,我看网上有带EMMC版本的,好像可以直接把系统镜像安装到EMMC里面。但是有个问题…...

Android官方ShapeableImageView描边/圆形/圆角图,xml布局实现

Android官方ShapeableImageView描边/圆形/圆角图&#xff0c;xml布局实现 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.…...

ubuntu扩大运行内存, 防止编译卡死

首先查看交换分区大小 grep SwapTotal /proc/meminfo 1、关闭交换空间 sudo swapoff -a 2、扩充交换空间大小&#xff0c;count64就是64G 1G x 64 sudo dd if/dev/zero of/swapfile bs1G count64 3、设置权限 sudo chmod 600 /swapfile 4、指定交换空间对应的设备文件 …...

Kafka集群修改单个Topic数据保存周期

在大数据部门经常使用Kafka集群&#xff0c;有的时候大数据部门可能在Kafka中的Topic数据保存时间不需要很长&#xff0c;一旦被消费后就不需要一直保留。默认Topic存储时间为7day&#xff0c;个别的Topic或者某台Kafka集群需要修改Topic数据保存的一个周期&#xff0c;调整为3…...

selenium模拟登录无反应

在使用自动化工具selenium时对某些网站登录界面send_keys输入账号密码&#xff0c;运行时却没有自己想要的结果出现&#xff0c;这是因为你碰到前端二般的开发人员&#xff0c;他们用的是HTML嵌套&#xff0c;这对后端人员造成了一些麻烦&#xff0c;废话不多说&#xff0c;直接…...

指针变量未分配空间或者初始化为空指针使用问题

提示&#xff1a;关于指针 文章目录 前言一、指针的使用总结 前言 在看c书籍的时候&#xff0c;看到浅复制和深复制时&#xff0c;说到成员为指针的时候&#xff0c;会出异常。但是其实没有更多的感想&#xff0c;但是联想到上次考试指针没分配空间导致程序异常的情况&#xf…...

力扣第763题 划分字母区间 c++ 哈希 + 双指针 + 小小贪心

题目 763. 划分字母区间 中等 相关标签 贪心 哈希表 双指针 字符串 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。 注意&#xff0c;划分结果需要满足&#xff1a;将所有划分结果按顺序连接&#xff0c;得…...

js 代码中的 “use strict“; 是什么意思 ?

use strict 是一种 ECMAscript5 添加的&#xff08;严格&#xff09;运行模式&#xff0c;这种模式使得 Javascript 在更严格的条件下运行。 设立"严格模式"的目的&#xff0c;主要有以下几个&#xff1a; 消除 Javascript 语法的一些不合理、不严谨之处&#xff0c…...

用于读取验证码的 OCR 模型

介绍 此示例演示了使用功能 API 构建的简单 OCR 模型。除了结合 CNN 和 RNN 之外,它还说明了如何实例化新层并将其用作“端点层”来实现 CTC 损失。 设置 import os import numpy as np import matplotlib.pyplot as pltfrom pathlib import Path from collections import Co…...

Uniapp 跳转回上一页面并刷新页面数据

比如我从A页面跳转到B页面 然后再从B页面返回到A页面 顺带刷新一下A页面数据 let pages getCurrentPages(); // 当前页面 //获取当前页面栈let beforePage pages[pages.length - 3]; // //获取上一个页面实例对象beforePage.$vm.reloadList(); //调用它方法然后跳转…...

DeOldify 接口化改造 集成 Flask

类似的图片修复项目 GFPGAN 的改造见我另一篇文 https://blog.csdn.net/weixin_43074462/article/details/132497146 DeOldify 是一款开源软件&#xff0c;用于给黑白照片或视频上色&#xff0c;效果还不错。 安装部署教程请参考别的文章&#xff0c;本文基于你给项目跑通&…...

Vue 3响应式对象: ref和reactive

目录 什么是响应式对象&#xff1f; Ref Reactive Ref vs Reactive 适用场景&#xff1a; 访问方式&#xff1a; 引用传递&#xff1a; 性能开销&#xff1a; 响应式对象优点 响应式对象缺点 总结 Vue 3作为一种流行的JavaScript框架&#xff0c;提供了响应式编程的…...

神眸低功耗芯片突破:让摄像头摆脱电线,2045年或迎1000亿只智能视觉终端!推理算力创业机会大

神眸低功耗芯片突破&#xff0c;开启智能视觉终端未来神眸致力于在算力浪潮下游&#xff0c;凭借极致低功耗芯片设计&#xff0c;使摄像头摆脱电线束缚&#xff0c;开启千亿只智能视觉终端的未来。杨作兴带领神眸实现了摄像头功耗降低一个数量级的突破。第一代芯片达到业界三分…...

VutronMusic:构建现代化跨平台音乐播放器的技术实现方案

VutronMusic&#xff1a;构建现代化跨平台音乐播放器的技术实现方案 【免费下载链接】VutronMusic 高颜值的第三方网易云播放器&#xff1b;支持流媒体音乐&#xff0c;如navidrome、jellyfin、emby&#xff1b;支持本地音乐播放、离线歌单、逐字歌词、桌面歌词、Touch Bar歌词…...

机器学习核函数原理与实战选型指南

1. 什么是机器学习中的核函数&#xff1f;它到底在解决什么问题&#xff1f;“Types of Kernels in Machine Learning”这个标题看起来像教科书目录里的一节&#xff0c;但如果你真在项目里调过SVM(kernelrbf)、用过sklearn.metrics.pairwise.rbf_kernel、或者被kernel trick这…...

避坑指南:S32K3 AUTOSAR环境安装后,如何验证MCAL配置与工程创建?

S32K3 AUTOSAR开发实战&#xff1a;从环境验收到MCAL配置全流程解析 当S32DS、EB tresos和RTD驱动安装完成后&#xff0c;许多开发者会陷入"工具链已就位&#xff0c;但不知从何入手"的困境。本文将带您跨越从环境安装到可编译工程的关键步骤&#xff0c;重点解决三个…...

HACS极速版终极指南:告别智能家居插件下载龟速的完整解决方案

HACS极速版终极指南&#xff1a;告别智能家居插件下载龟速的完整解决方案 【免费下载链接】integration &#x1f1e8;&#x1f1f3; HACS 极速版&#xff0c;无需登陆Github 项目地址: https://gitcode.com/gh_mirrors/int/integration 你是否曾经为了给Home Assistant…...

Windows 环境下 NVM 安装与 Node.js 版本管理完全指南

&#x1f4a1; 为什么需要 NVM&#xff1f; 作为前端开发者&#xff0c;你是否遇到过这些困扰&#xff1a; 场景痛点新项目要求 Node 20&#xff0c;老项目依赖 Node 16频繁卸载重装&#xff0c;浪费时间团队协作时环境不一致代码在同事电脑上跑不通全局安装的依赖版本冲突升…...

Unity WebGL文本输入解决方案:WebGLInput原理与集成指南

1. 为什么Unity WebGL的文本输入让人反复抓狂“WebGL平台不能打字”——这句话在Unity开发者社区里出现的频率&#xff0c;几乎和“打包报错”“内存泄漏”一样高。我第一次遇到这个问题是在2021年&#xff0c;给一个教育类Web应用做跨平台迁移&#xff1a;iOS和Android端的Inp…...

如何用Sumo-RL构建智能交通信号系统:完整强化学习实战指南

如何用Sumo-RL构建智能交通信号系统&#xff1a;完整强化学习实战指南 【免费下载链接】sumo-rl Reinforcement Learning environments for Traffic Signal Control with SUMO. Compatible with Gymnasium, PettingZoo, and popular RL libraries. 项目地址: https://gitcode…...

利用Taotoken模型广场为不同AI任务选择最佳模型

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 利用Taotoken模型广场为不同AI任务选择最佳模型 在实际开发中&#xff0c;我们常常面临一个选择&#xff1a;面对内容生成、代码编…...

从账单视角看 Taotoken Token Plan 套餐带来的月度成本优化

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 从账单视角看 Taotoken Token Plan 套餐带来的月度成本优化 效果展示类&#xff0c;通过分享一个中型项目在采用 Taotoken 按 toke…...