浅谈C#中的值类型和引用类型

news/2024/9/18 9:02:06/文章来源:https://www.cnblogs.com/xiacuncun/p/18376335

1. 值类型

  • 常见的值类型:int/long/short/byte/float/double/bool/char/Struct(用户建立的结构体通常是值类型的)/Nullable Types(这是一个特殊的值类型,表示一个正常值或者空,比如int?)
  • 值类型的例子:
int a=10;
int b=a;Console.WriteLine($"a:{a}");//a:10
Console.WriteLine($"b:{b}");//b:10b=20;Console.WriteLine($"a:{a}");//a:10,原始值不受影响
Console.WriteLine($"b:{b}");//b:20,只有b的值改变了
  • 值类型直接存储在内存(称之为栈(STACK),栈以LIFO访问,后进栈的数据先被访问,栈的大小是固定的,不是动态分配的,所以访问速度快)中,当把一个值赋值给另外一个变量时,其实是把变量的值复制给了新的变量,而不会改变原有值(a=10;b=a;b=20;这个例子中并不会因为b变成20了就反过来使a也变成20了,因为这个过程是复制,副本虽然变了,但是a=10这个原始值一直没有被改变。)
  • 当一个方法传递值类型的参数时(包括结构体),会将参数的值复制到函数的参数中,对参数的修改不会影响到原始变量;

2. 引用类型

  • 类,接口,委托,数组,字符串(字符串比较特殊,他可以像值类型一样用,但是它又具有不可变性。)
  • 用new动态分配内存,由GC(垃圾回收器)释放
  • 引用类型实际上操作的是地址。在C#中,你需要获取引用类型实例的地址只需要用&,如下
string str = "Hi";
IntPtr address = new IntPtr(&str)
  • 但是当想要在值类型实例上获得地址,就变得很困难,你可能需要先把该值类型封装在一个引用类型(比如元素是值类型的数组类型)中,然后再获取该引用类型的地址。
  • 在值类型传参时使用ref,ref实际上传入的不是值类型的值(副本),而是值类型的引用(地址)。如下:
public static void ModifyValue(ref int num)
{num = 42;
}
public static void Main()
{int value = 10;Console.WriteLine(Value);// output 10ModifyValue(ref value);Console.WriteLine(value);// output 42
}
  • 引用类型存储在内存的堆(Heap),动态分配,当在堆上分配了实例之后(new之后),访问该实例实际上是通过访问该实例的内存地址来访问该实例的。

3. 由值类型和引用类型不同引发的问题案例

  • 如下,有一个方法,这个方法尝试把一个字符串切分后的值准确的赋值给结构体实例:
    public static T ParseString2Struct<T>(string in_str) where T : struct{T result = default(T);//Type type = result.GetType();//object clone_result = Activator.CreateInstance(type);FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public);object parsedValue;var lines = in_str.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);if (lines.Length != fields.Length){throw new ArgumentException("richtextbox string length does not match the structer.");}for (int i = 0; i < fields.Length; i++){var lineParts = lines[i].Split(':');if (lineParts.Length != 2 || lineParts[0].Trim() != fields[i].Name){throw new ArgumentException("richtextbox string format does not match the structer.");}var value = lineParts[1].Trim();var fieldType = fields[i].FieldType;try{parsedValue = Convert.ChangeType(value, fieldType);}catch (Exception){throw new ArgumentException("richtextbox string does not match the structer.");}if (!fields[i].IsInitOnly){fields[i].SetValue(result, parsedValue);}          }//result = (T)clone_result;return result;}
}
  • 在这个方法里,使用T result = default(T);初始化值类型,这里不管是使用default(T)还是new T()其实本质都是获取了一个全新的值类型副本,但是default和new之间有一些小区别:用default的时候不会去获取结构构造函数中的初始值,而是直接使用该字段的数据类型的默认初始值。用new的时候程序会去扫描并使用该结构体构造函数中的初始值
  • 下面这句话用于调用该方法:
Ds64_65 ds6465 = DataSet_lib.ParseString2Struct<Ds64_65>(str);
  • 实际调试中发现一个很奇怪的现象,不管parsedValue值是多少,result的字段无论怎样都不能被赋值:
    • FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public);用来设定结构体字段的public属性。
    • fields[i].IsInitOnly用来检查每个字段都没有设置只读属性。
    • 为了检查FieldInfo[]类中的方法是否适用,甚至通过修改结构体构造函数的初始值再用new T()创建一个新的初始结构体,然后使用GetValue()方法,可以正常获得初始化值。
  • 通过询问AI才知道SetValue()方法对于值类型的注意点:
    • 1.值传递是传递的字段的副本,副本的改变对字段本身的值没有影响。
    • 2.传递的值和字段类型是兼容的,不兼容会抛异常,这一条上面的方法可以确保。
    • 3.对于结构体中的字段,SetValue()方法只会修改字段的副本,而不是原始结构体实例。意味着在使用SetValue()修改结构体之后,需要将修改后的副本重新赋值原始结构体实例.
  • SetValue()这个方法本身又不能传入ref类型,所以不能靠ref把修改后的值反馈回来。
  • 对于修改建议,AI建议是使用SetValueDirect:
    • __makeref():用于获取结构体字段的引用,底层特性,不建议直接使用
typeof(T).GetFields()[i].SetValueDirect(__makeref(result),parsedValue)
  • 通过GitHub上参考的代码, 找到一个更合适的解决方法,就是创建一个Clone,再把Clone赋值回去,如下:
    • 注意此方法生成的object clone_result,它是一个object类型,引用类型。那就不存在上面值类型放进SetValue()里的那些问题。最后再强转回T,给到result输出。
 public static T ParseString2Struct<T>(string in_str) where T : struct{T result = default(T);Type type = result.GetType();object clone_result = Activator.CreateInstance(type);FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public);object parsedValue;var lines = in_str.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);if (lines.Length != fields.Length){throw new ArgumentException("richtextbox string length does not match the structer.");}for (int i = 0; i < fields.Length; i++){var lineParts = lines[i].Split(':');if (lineParts.Length != 2 || lineParts[0].Trim() != fields[i].Name){throw new ArgumentException("richtextbox string format does not match the structer.");}var value = lineParts[1].Trim();var fieldType = fields[i].FieldType;try{parsedValue = Convert.ChangeType(value, fieldType);}catch (Exception){throw new ArgumentException("richtextbox string does not match the structer.");}if (!fields[i].IsInitOnly){fields[i].SetValue(clone_result, parsedValue);}          }result = (T)clone_result;return result;}
  • Clone可以成功解决该问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/786142.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图解Kafka:Kafka架构演化与升级!

了解了 Kafka 架构就掌握了 Kafka 最核心的知识,Kafka 作为业界最知名、最流行的消息系统和流式处理组件,在面试中和日常工作中经常会见到。那么今天,我们就来聊聊 Kafka 的架构演化与升级,并通过图解的方式让你一目了然。 1.Kafka 初印象 Kafka 最初由 LinkedIn 公司开发,…

Nginx配置SSL证书:轻松实现网站的HTTPS加密!

成功配置SSL证书后,您将能够通过HTTPS加密通道安全访问Nginx服务器。一、准备材料 SSL证书绑定的域名已完成DNS解析,即您的域名与主机IP地址相互映射。您可以通过DNS验证证书工具,检测域名DNS解析是否生效。具体操作: 【1】登录数字证书管理服务控制台。 【2】在左侧导航栏…

为什么通过clear_refs可以使进程触发缺页?

平台 ARM64 Linux 6.10 作者 pengdonglin137@163.com 背景 最近在学习Linux的缺页异常时突然奇想,在不进行内存换出的情况下,如何让进程再次触发缺页? 基于对ARMv8的理解,它的MMU的页表项中有个AF位,当AF为0时,当访问到对应的虚拟页时,会触发缺页。如果AF位为0,当访问到…

复选框单选

const lastDataHandle = ref(null); // 上次选择的dataHandle值 const dataHandle: any = ref([]); const dataHandleALLList = [{ label: 无, value: 0 },{ label: 上传照片, value: 1 },{ label: 二维建模, value: 2 }, ]; // 单选 function setLastDataHandle() {lastDataHa…

解码“智慧市政”的创新实践与战略意义

在这个日新月异的信息时代,智慧城市建设已成为全球共识,而“智慧市政”作为其核心组成部分,正以前所未有的速度重塑城市管理和服务模式。智慧市政:定义与价值智慧市政,是指利用先进的信息技术,如地理信息系统(GIS)、物联网(IoT)、大数据分析、人工智能(AI)等,对城…

WPF 模仿前端大佬写一个Hover效果

先看一下效果吧: 原博主的地址:【动画进阶】神奇的卡片 Hover 效果与 Blur 的特性探究 - ChokCoco - 博客园 (cnblogs.com) 原效果是一个css效果,我们采用WPF的方式模仿一下 因为技术有限,没有原博主的那么好看,毕竟盗版永远比不过原版... 然后这里看一下盗版的怎么写吧 先…

信息收集利器|一款功能强大的子域收集工具

01工具介绍 (下载地址见最后) 在hw等攻防演练中,信息收集做为演练厨师阶段最重要的步骤,方式方法尤为重要,好的工具达到事半功倍的效果。OneForAll是一款集百家之长,功能强大的全面快速子域收集终极神器。 解决以下痛点: 在渗透测试中信息收集的重要性不言而喻,子域收集…

[poc] hw情报-泛微 e-cology v10 远程代码执行漏洞

漏洞介绍 (poc下载地址见最后)泛微披露了e-cology远程代码执行漏洞。该漏洞允许攻击者通过e-cology-10.0前台获取管理员访问令牌,然后利用JDBC反序列化,实现远程代码执行。 漏洞描述通过/papi/passport/rest/appThirdLogin接口获取管理员账号票据,根据该票据获取访问令牌,…

Tita的OKR :产品经理的OKR

产品经理制定的OKR,对组织发展有重大的意义,它能促使产品经理,产品团队,乃至是公司全体员工走出舒适区,超越能力边界。正因为挑战的存在,才使得产品经理才有忧患意识,不断改进产品,从而适应竞争激烈的市场。只有不断改善、创新和突破,才能让产品经理的能力不断提升,才…

About Living 生存之道

About Living 生存之道 爱 健康 财富 是人生值得追求的东西!人生不过是一段体验。我们都是时间的囚徒,活在当下。有趣!有料! 一个人的命运啊,当然要靠自我奋斗,但是也要考虑到历史的行程---奋斗是加法,趋势是乘法 我相信这世界上,有些人有些事有些爱,在见到的第一次,…

JAVA 封装

封装 1.“高内聚,低耦合” 2.属性私有 alt+insert创建get/set方法,方法内部可以进行判断参数是否合法 意义:1.提高程序的安全性,保护数据 2.隐藏代码的实现细节 3.统一接口(get/set方法) 4..增加系统的可维护性 成员变量的隐藏 “就近原则”,子类新定义的方法可以…

怎么都在劝我用通义灵码

听朋友说最近通义灵码有个活动,分享体验心得就有机会抽 iPhone 15。而且通过活动第一次使用通义灵码的新用户,还人均送一个“显眼包”。听朋友说最近通义灵码有个活动,分享体验心得就有机会抽 iPhone 15。而且通过活动第一次使用通义灵码的新用户,还人均送一个“显眼包”。…