C#--深入理解类型
今日无事,回顾了一下C#基础知识,颇有收获,就自己的理解,写了这篇文章,如有不对,欢迎指正。
C#中的类型可以分为两类:值类型与引用类型,如下图所示。
值类型通常被分配到线程的堆栈上,而引用类型则被分配到托管堆上。例如下面例子:
valuetype和reftype在内存中的位置如下所示:
从上面可以看出,值类型与引用类型的区别在于实际数据的存储位置:
值类型的变量和实际数据都存储在堆栈中;而引用类型只有变量存储在堆栈中,变量储存着实际数据的地址,实际数据储存在与地址相对应的托管堆中。
但是,上边所说的情况并不是绝对的,也就是说值类型的实例不一定分配到线程堆上。
1、应用类型中嵌套定义值类型
以上代码内存分配情况如下:
如果类的字段类型是值类型,他将作为引用类型实例的一部分,被分配到托管堆中。但那些作为局部变量(例子中的c变量)的值类型,则依然会被分配到托管堆中。
2.值类型中嵌套定义引用类型
内存分配如下:
值类型实例总会被分配到它声明的地方,声明的是局部变量时,将分配到栈上,而声明为引用类型成员时,则被分配到托管堆上;而引用类型实例总是被分配到托管堆上。
值类型与引用类型除了在内存上分布方面的区别,还有以下几个方面的区别:
1、值类型继承自ValueType,ValueType又继承自System.Object;而引用类型则直接继承自System.Object;
2、值类型不收GC(垃圾回收器)控制,作用域结束时操作系统自行释放,从而减少了托管堆的压力;
3、若值类型是密封的(sealed),你将不能把值类型作为其他任何类型的基类;而引用类型则一般具有继承性,这里指的是类和接口;
4、值类型不能为null,他会默认初始化为数值0;而引用类型默认情况下会初始化为null值,表示不指向托管堆中的任何地址。对值为null的引用类型的任何操作,都会引发NullReferenceExcption异常;
5、由于值类型包含其实际数据,因此在默认情况下,值类型之间的参数传递不会影响变量自身;而引用类型变量保存的是数据的引用地址,他们作为参数被传递时,参数会发生改变,从影响引用类型变量的值。
两大类型的转换--装箱和拆箱
隐式转换:由低级别类型向高级别类型转换的过程;
显式转换:也叫强制类型转换;
通过is和as运算符进行安全的类型转换;
通过.NET类库中的Convert类来进行类型转换;
装箱拆箱的概念:
装箱:将值类型转换为引用类型的过程;
拆箱:将引用类型转换成值类型的过程;
装箱过程中,系统会在托管堆中生成一份托管堆中值类型对象的副本;而拆箱则是从托管堆中将引用类型所指向的已装箱数据复制回值类型对象的过程。
下面介绍装箱与拆箱过程:
1、装箱过程
具体分为以下三个步骤:
(1)内存分配:在托管堆中分配好内存空间以存放复制的实际数据;
(2)完成实际数据的复制:将值类型的实际数据复制到新分配的内存中;
(3)地址返回:将托管堆中的对象地址返回给引用类型变量;
拆箱过程:
(1)检查实例:首先检查要进行拆箱操作的引用类型变量是否为null,如果为null则抛出NullReferenceException异常;如果不为null则继续检查变量是否和拆箱后的类型是同一类型,若结果为否,会导致InvalidCastException异常。
(2)地址返回:返回已装箱变量的实际数据部分的地址;
(3)数据复制:将托管堆中的实际数据复制到栈中;
总结:
由上面图解可以知道,如果程序中有过多的拆箱装箱操作,由于两个过程都需要进行数据复制,该操作会大量额外运行时间,并且在装箱拆箱过程中必须会产生多余的变量,进一步加重了GC的负担,导致程序的性能降低。
参数传递问题剖析:
1、值类型参数的按值传递:
参数分为两类:形参与实参。形参指的是被调用方法中的参数,也就是说方法定义中的参数为形参;实参指的是调用方法时,传递给对应参数的值。
对值类型的按值传递,传递的是该值类型实例的一个副本,也就是说形参接受到的是实参的一个副本,被调用方法操作的是实参的一个副本而已;
2、引用类型的按值传递
当传递的参数是引用类型时,传递和操作的目标是指向对象的地址,而传递的实际内容是对地址的复制。由于地址指向的是实参的值,当方法对地址进行操作时,实际上操作了地址所指向的值,所以调用方法后原来实参的值就会被修改。
3、string引用类型参数按值传递的特殊情况
虽然string类型为引用类型,然而在按值传递时,传递的实参却不会因为方法中形参的改变而被修改。
string具有不变形,一个string被赋值,则不能再通过代码去修改它的值。上边代码看似oldStr被赋予了新值“New string”,但实际上内存会重新分配一块内存空间来存放“New string”字符串,然后把分配的内存首地址赋给oldStr变量。
4、值类型和引用类型参数的按引用传递
不管是值类型还是引用类型,你都可以使用ref或out关键字来实现参数的按引用传递。并且在按引用传递时,方法的定义和调用都必须显示的调用ref或out关键字,否则会发生编译错误。
在按引用传递时,不参数是值类型还是引用类型,其本质都是一样的,都是通过ref或out关键字来告诉编译器,方法传递的是参数地址而不是参数本身。
C#--深入理解类型的更多相关文章
- List分组迭代器 C#--深入理解类型
List分组迭代器 说明: 针对长度较大的List对象,可以分组批量进行处理, 如:长度为1000的List对象,可分为10组,每组100条,对数据进行业务逻辑处理... Source /**** ...
- JavaScript漫谈之理解类型操作符typeof
在本文中,将简述JavaScript类型系统和数据类型,以及如何使用typeof操作符执行类型检查. 还讲解了使用typeof操作符进行某些数据类型检查是不完善的,并介绍其他几种类型检查的方法. 更多 ...
- Java泛型中的类型擦除机制简单理解
Java的泛型是JDK1.5时引入的.下面只是简单的介绍,不做深入的分析. Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...
- Java泛型-内部原理: 类型擦除以及类型擦除带来的问题
一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...
- Java泛型类与类型擦除
转载自:http://blog.csdn.net/lonelyroamer/article/details/7868820 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型. ...
- java泛型 8 泛型的内部原理:类型擦除以及类型擦除带来的问题
参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...
- C++ Primer 5th 第2章 变量和基本类型
*****代码在Debian g++ 5.3.1 / clang++ 3.8(C++11)下编写调试***** 由于部分编译器对标准遵循的不同以及自身额外的扩展,本章书中的少数知识点与实际实现存在偏差 ...
- <转>泛型的内部原理:类型擦除以及类型擦除带来的问题
参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...
- Java中泛型 类型擦除
转自:Java中泛型是类型擦除的 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类 ...
随机推荐
- Postman-----构建工作流程(用于某个请求完成后从指定的请求开始继续运行)
使用场景: 当您开始运行某个集合时,所有的请求按照您在主程序中看到的顺序运行,但是在某些情况下,往往我们希望按顺序列出的请求A.B.C.D.E.F请求,在执行时不执行B.C请求,希望A请求完成后直接执 ...
- nginx日志模块及日志定时切割
一:作用 Ngx_http_log_module:定义日志格式,并且以指定的格式保存. 二:示例配置 log_format compression '$remote_addr - $remote_us ...
- OsharpNS轻量级.net core快速开发框架简明入门教程-代码生成器的使用
OsharpNS轻量级.net core快速开发框架简明入门教程 教程目录 从零开始启动Osharp 1.1. 使用OsharpNS项目模板创建项目 1.2. 配置数据库连接串并启动项目 1.3. O ...
- python接口自动化(二十六)--批量执行用例 discover(详解)
简介 我们在写用例的时候,单个脚本的用例好执行,那么多个脚本的时候,如何批量执行呢?这时候就需要用到 unittest 里面的 discover 方法来加载用例了.加载用例后,用 unittest 里 ...
- FreeSql.Repository 通用仓储层功能
前言 好多年前,DAL 作为数据库访问层,其实是非常流行的命名方式. 不知道从什么时候开始,仓储层成了新的时尚名词.目前了解到,许多人只要在项目中看见 DAL 就会觉得很 low,但是比较可笑的一点是 ...
- volitale、synchronized、RetreenLock区别
synchronized和RetreenLock锁区别 1.synchronized是java关键字,RetreenLock是个java类 2.synchronized无法获取锁状态,Lock可以判断 ...
- 5G+边缘计算,着眼可见的未来
在 2019 年 2 月巴塞罗那举办的 MWC(世界移动通讯大会)上,华为手机带来了一款超薄的 5G 折叠屏手机 Mate X.这款手机将折叠屏和 5G 结合在一起,引起了不少人的关注与舆论,而昂贵的 ...
- docker(1)应用场景以及安装
今年来了新公司,公司没有用什么新技术,架构就简单的前后分离,但是我推一下新的技术,在这基础上我要培训一下同事,让他们能接受,对新技术不感到陌生,并且认可愿意去学习.其实在这个过程中也能让他们认同我这个 ...
- SAP MM 预留单据的历史修改记录?
SAP MM 预留单据的历史修改记录? 在笔者眼里,SAP系统是一个高度严谨的软件系统.用户在SAP系统里的相关操作,系统都会做记录.用户对于系统的相关单据的增删改,SAP系统都有保留change h ...
- Android Glide详细使用教程
此处我只是做个记录,后边再补充 原文地址:http://www.jufanshare.com/content/35.html 这篇文章写的比较清楚,还附有Demo代码.算是不错的Android Gli ...