精华内容
下载资源
问答
  • C#值类型与引用类型区别,和一些基础算法,刚入门的小伙伴,希望对你有用
  • C#值类型与引用类型

    千次阅读 2019-03-20 17:22:02
    类型 C#中的类型指的是{类,结构,接口,枚举,...类型可以分为值类型以及引用类型,没有第三种情况 值类型:结构和枚举 引用类型:类、接口、指针、字符串、委托、数组 引用类型Reference Type 内存布局 引用...

    类型

    C#中的类型指的是{类,结构,接口,枚举,委托}中的任意一个成员。类型(type)和(class)不同,后者是前者的的一个特殊情况,任何拥有某个类型的(value)被称为某类型的一个实例(instance)。

    类型分类

    类型可以分为值类型以及引用类型,没有第三种情况

    • 值类型:结构和枚举
    • 引用类型:类、接口、指针、字符串、委托、数组

    引用类型Reference Type

    内存布局

    引用类型的内存分配永远是两部分

    1. 引用它的对象
    2. 堆上的一个对象

    其中堆上的对象可以被如下的对象引用:

    • 栈上的一个变量(最常见的情况)
    • P/Invoke情形下的句柄表
    • Finalizer queue终结队列
    • 寄存器在这里插入图片描述
      引用类型在申请内存时,需要计算它本身所需要的内存以及它的父类成员需要的内存,一直算到System.Object(不过它没有成员,所以一般没有指定父类的引用类型计算内存就只需要计算它自己就够了,因为对于没有指定父类的引用类型来说,其父类为System.Object)。

    内存布局(以32位机为例):
    4. 同步块索引:内存的分配从同步块索引开始,它占据4个字节,栈上的引用指向同步块索引的后边的部分,所以同步块索引占据-4字节到0
    5. 方法表指针(又叫类型对象指针):占据4个字节(指向方法表,位于类型对象中,而类型对象一般位于同一个应用程序域的加载堆中)方法表指针与同步块索引这8个字节(64位机上为16字节,分别占据8字节)是每个引用类型都一定会有的,为了确保类型安全性,C#是无法操作它们的
    6. 类型所有父对象的实例成员(静态成员存储在类型对象中),其中,所有引用类型成员都分配4字节,因为只需要分配地址,分配顺序不定,CLR会尽享消除字段对齐带来的负面影响
    7. 类型自己的实例成员(静态成员存储在类型对象中),引用类型成员分配同上

    默认值

    所有引用类型的默认值都为null,可以通过令某个引用类型变量null,来将它与某个堆上的对象之间的关联切断,此时,该引用类型变量将不指向任何堆上的对象,称为垃圾,等待垃圾回收

    同步块索引

    是类的标准配置,位于类在堆上定义的开头-4(或-8)至0字节

    功能

    在线程同步中用来判断对象是被使用还是闲置。默认情况下,同步块索引被赋予一个特殊值,此时对象没有被线程独占。当一个线程拿到对象,并打算对其操作时,会检查对象的同步块索引。如果索引为特殊值,说明没有任何线程正在操作它,此时这个线程获得它的操作权。同时在CLR的同步块数组中添加一个新的同步块,并将该块的所引致写入实例的同步索引值中。此时如果有其他线程来访问该实例,它就不能操作这个实例了,因为它的同步块索引值部位特殊值。当独占线程操作完毕后,同步块所索引的值被重设回特殊值。

    方法表指针(类型对象)

    类型对象由CLR在加载堆中创建,创建时机为加载该程序集时
    类型对象重要组成部分:

    1. 类型的静态字段
    2. 方法表

    创建之后这两部分都不会改变,这也是静态字段全局性的由来,它们被所有的该类型的实例共享。
    可通过以下代码来验证类型对象在内存上的唯一性:

    var a = new AStruct();
    var b = new AStruct();
    Console.WriteLine(ReferenceEquals(a.GetType(), b.GetType())); // True
    

    方法表

    类型所有的方法,包括静态方法和实例方法。

    引用类型的复制

    引用类型的复制分为深复制和浅复制(默认情况)

    • 浅复制:只会复制地址本身,然后将这个地址复制给新的变量,新的对象和旧的对象同时指向堆上旧的实例对象,更改任何一个成员的值都会影响另一个
    • 深复制:会在堆上创建新的类型实例
      浅复制验证如下:
    var a = new AClass();
    a.a = 1;
    a.b = "hey";
    var b = a;
    b.a = 2;
    Console.WriteLine(a.a); // 输出2
    

    执行完毕之后内存布局如下图:
    在这里插入图片描述

    如何实现深拷贝

    通过实现ICloneable接口并实现Clone方法

    public class AClass : ICloneable
    {
    	public int a;
    	public string b;
    	public AClass(int aa, string bb)
    	{
    		a = aa;
    		b = bb;
    	}
    	public object Clone()
    	{
    		return new AClass(a, b);
    	}
    }
    

    如此实现之后AClass类支持了深复制,故其内存布局变为下图所示:
    在这里插入图片描述

    值类型Value Type

    对应类型:结构枚举

    通常来说,值类型就是字面意义上的那种值,例如整数int、浮点数float/double,布尔值等。实际上,整数、浮点数、布尔值等都是结构体

    Boolean类型定义

    namespace System
    {
    	public struct Boolean : IComparable, IComparable<Boolean>, IConvertible, IEquatable<Boolean>
    	{
    		public static readonly string FalseString;
    		public static readonly string TrueString;
    
    		public static Boolean Parse(string value);
    		public static Boolean TryParse(string value, out Boolean result);
    		public int CompareTo(Boolean value);
    		public int CompareTo(object obj);
    		public Boolean Equals(Boolean obj);
    		public override Boolean Equals(object obj);
    		public override int GetHashCode();
    		public TypeCode GetTypeCode();
    		public override string ToString();
    		public string ToString(IFormatProvider provider);
    	}
    }
    

    int类型定义

    using System.Globalization;
    
    namespace System
    {
    	public struct Int32 : IComparable, IComparable<Int32>, IConvertible, IEquatable<Int32>, IFormattable
    	{
    		public const Int32 MaxValue = 2147483647;
    		public const Int32 MinValue = -2147483648;
    
    		public static Int32 Parse(string s);
    		public static Int32 Parse(string s, NumberStyles style);
    		public static Int32 Parse(string s, IFormatProvider provider);
    		public static Int32 Parse(string s, NumberStyles style, IFormatProvider provider);
    		public static bool TryParse(string s, out Int32 result);
    		public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out Int32 result);
    		public Int32 CompareTo(object value);
    		public Int32 CompareTo(Int32 value);
    		public bool Equals(Int32 obj);
    		public override bool Equals(object obj);
    		public override Int32 GetHashCode();
    		public TypeCode GetTypeCode();
    		public override string ToString();
    		public string ToString(IFormatProvider provider);
    		public string ToString(string format);
    		public string ToString(string format, IFormatProvider provider);
    	}
    }
    

    默认值

    大多数值类型默认值都为0,例如整数、浮点数的默认值为0,枚举类型的默认值也为0,char的默认值为’\0’

    内存分配

    分为以下三种情况讨论

    1. 值类型作为局部变量
    2. 值类型作为引用类型的成员
    3. 值类型中包含引用类型

    值类型作为局部变量

    普通的值类型总是分配在栈上。例如简单的int为例,int i = 1,意味着在栈上开辟了一块空间存储了这个值类型,但是int是一个结构体,它存在2个值类型的成员(最大值,最小值),这两个类型为常量(const = static readonly),故其存在于加载堆中。新建一个int不会复制其最大值和最小值,其开销永远只有4字节,即其自身,即使是64位机上int也是4字节,因为其本质为Int32
    对于局部变量的值类型来说,其复制将只复制值的副本,其更改不会对原值有影响

    var i = 1;
    var j = i;
    i = 2;
    Console.WriteLine(j); // 输出1
    

    值类型作为引用类型的成员

    如果值类型为引用类型的成员,则遵从引用类型的内存分配与复制方式

    public class AClass
    {
    	public int a;
    	public string b;
    }
    

    在创建一个该类的实例时,遵从引用类型的分配方式

    var a = new AClass();
    a.a = 1;
    a.b = "hey";
    

    执行完上述代码后,线程栈上会开辟空间存储一个引用,其指向堆上的AClass类的实例,此时的值类型a.a = 1存储在堆上
    在这里插入图片描述

    值类型中包含引用类型

    如果一个结构体中包含了引用类型,例如包含了一个字符串或者类,则它引用的那部分会遵从引用类型的内存分配,值类型那部分则遵从值类型创建的内存分配

    例如我们有如下的类和结构体

    public class AClass
    {
    	public int a;
    	public string b;
    }
    
    public struct AStruct
    {
    	public AClass ac;
    	public double c;
    }
    

    则新建这个值类型的实例时,它的引用类型成员则会遵守引用类型的内存分配方式,值类型成员则存储在栈上

    var a = new AStruct();
    a.ac = new AClass();
    a.c = 1;
    a.ac.a = 2;
    a.ac.b = "";
    

    此时的内存分配情况如图
    在这里插入图片描述
    为了弄清楚其内存结构,我们复制它,并尝试修改其成员的值

    var b = a;
    b.c = 999;
    b.ac.a = 888;
    b.ac.b = "bye";
    
    Console.WriteLine(a.c); // 1
    Console.WriteLine(a.ac.a); // 888
    Console.WriteLine(a.ac.b); // bye
    

    此时内存情况如图
    在这里插入图片描述
    通过如下代码可以验证a.ac和b.ac指向同一个对象,而a.c和b.c没有关系

    Console.WriteLine(ReferenceEquals(a.ac, b.ac)); // True
    Console.WriteLine(ReferenceEquals(a.c, b.c)); // False
    

    合适考虑使用值类型

    值类型可以认为是轻量级的引用类型,其设计目的就是提高程序性能,如果所有类型都是引用类型,则会大大降低性能,主要是因为:

    • 每次新声明变量都要创建类型对象指针和同步块索引
    • 内存分配必定牵扯到堆,增加GC压力

    当结构体的全部属性都是值类型时,结构体不会和堆扯上关系(例如,int就是这样的结构体),这样可以减轻GC的压力,选择结构体可在初始化时提高性能,因为其初始化不需要生成引用类型标配的类型对象指针和同步块索引

    适合使用结构体的情况

    • 当对象的所有属性都需要在创建之初即赋值时
    • 当对象的全部属性都是值类型时(如果存在引用类型,就会牵扯到内存分配到堆上的问题,无法减轻GC压力)
    • 当前对象不需要被继承时

    例如二维坐标(包括两个double)、长方形(包括长、宽、高)这样的对象适合使用结构体

    值类型和引用类型的区别与联系

    区别

    • 所有值类型隐式派生自System.ValueType。该类确保值类型所有的成员全部分配在栈上。有三个例外:
      1. 结构体如果含有引用类型的成员,该成员也会牵扯到堆的分配
      2. 静态类型,如果一个变量是静态类型,则无论它是什么类型,都会分配到加载堆上
      3. 局部变量被捕获升级为密封类
    • 引用类型的初值为null,值类型为0
    • 对于引用类型,栈中会有一个变量名和变量类型,指向堆中对象实例的地址。值类型尽有栈中的变量名和类型,没有指向实例的指针
    • 值类型不能被继承,引用类型则可以
    • 值类型的生命周期为其定义域。值类型离开其定义域后将被立刻销毁,引用类型则会进入垃圾回收分代算法,销毁时间待定
    • 值类型的构造函数必须为所有成员赋值
    • 值类型没有同步块索引,不能作为线程同步工具

    联系

    • 值类型和引用类型可以通过装箱和拆箱互相转化
    • 所有类型都派生自System.ValueType,它是System.Object的子类
    • 类和结构体都可以实现接口,结构体如int,DateTime等都实现了IComparable接口,使得他们可以比较大小
    展开全文
  • 7理解C#值类型与引用类型[整理].pdf
  • C# 值类型与引用类型

    千次阅读 2018-07-19 14:31:09
    类型与引用类型 只所以要提这两个概念,是因为很好得理解这两个概念有助于我们写出比较高效的C#代码。 我们知道,C#中的每一种类型要么是值类型,要么是引用类型。所以每个对象要么是值类型的实例,要么是引用...

    值类型与引用类型

    只所以要提这两个概念,是因为很好得理解这两个概念有助于我们写出比较高效的C#代码。

    我们知道,C#中的每一种类型要么是值类型,要么是引用类型。所以每个对象要么是值类型的实例,要么是引用类型的实例。

    引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即直接继承System.ValueType。

    作为所有类型的基类,System.Object提供了一组方法,这些方法在所有类型中都能找到,其中包含toString方法及clone等方法。

    System.ValueType直接继承System.Object,即System.ValueType本身是一个类类型,而不是值类型;System.ValueType没有添加任何成员,但覆盖了所继承的一些方法,使其更适合于值类型。例如,ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

    http://images2015.cnblogs.com/blog/155497/201704/155497-20170416230127774-1182260202.png

    简单了解了值类型与引用类型那么我们下面来看下C#中的装箱和拆箱的概念。

    装箱和拆箱

    装箱和拆箱是值类型和引用类型之间相互转换是要执行的操作。

    1.    装箱在值类型向引用类型转换时发生

    2.    拆箱在引用类型向值类型转换时发生

    1 object objValue = 4;

    2

    3 int value = (int)objValue;

      

    上面的两行代码会执行一次装箱操作将整形数字常量4装箱成引用类型object变量objValue;然后又执行一次拆箱操作,将存储到堆上的引用变量objValue存储到局部整形值类型变量value中。

    同样我们需要看下IL代码:

     1 .locals init (

     2

     3 [0] object objValue,

     4

     5 [1] int32 'value'

     6

     7 ) //上面IL声明两个局部变量object类型的objValue和int32类型的value变量

     8

     9 IL_0000: nop

    10

    11 IL_0001: ldc.i4.4 //将整型数字4压入栈

    12

    13 IL_0002: box [mscorlib]System.Int32 //执行IL box指令,在内存堆中申请System.Int32类型需要的堆空间

    14

    15 IL_0007: stloc.0 //弹出堆栈上的变量,将它存储到索引为0的局部变量中

    16

    17 IL_0008: ldloc.0//将索引为0的局部变量(即objValue变量)压入栈

    18

    19 IL_0009: unbox.any [mscorlib]System.Int32 //执行IL 拆箱指令unbox.any 将引用类型object转换成System.Int32类型

    20

    21 IL_000e: stloc.1 //将栈上的数据存储到索引为1的局部变量即value

     

    拆箱操作的执行过程和装箱操作过程正好相反,是将存储在堆上的引用类型值转换为值类型并给值类型变量。

     

    展开全文
  • c#值类型与引用类型

    千次阅读 2014-08-30 12:11:57
    引用类型与值类型 值类型是个轻量级类型,引用类型是个 '重量级‘类型。 值类型存放的是对象本身,而引用类型存放的对象存放的内存地址 1.内存分配 值类型 的实例一般在线程栈上分配, 引用类型的实例则在托管堆上...

    一.引用类型与值类型

    值类型是个轻量级类型,引用类型是个 '重量级‘类型。

    值类型存放的是对象本身,而引用类型存放的对象存放的内存地址

    1.内存分配

    值类型 的实例一般在线程栈上分配, 引用类型的实例则在托管堆上分配。

    注: 值类型的实例并不是一定在线程栈上,和定义的位置也有关系,局部变量和方法参数一定在栈上,但是类的成员变量总是会有类中的其他数据分配在一起,就有可能分配在堆上

    C#同C/C++不同,c#是基于.net环境的,称之为托管。因为之前学的是C语言所以很不理解。

    比如C语言中变量存放在 堆还是栈中并不是有变量类型来决定。 而是由定义变量的方式。栈中存放局部变量的值,系统进行内存分配回收。堆则是程序员创建,回收。

    而在C#托管代码中变量存放内存的位置则不是根据定义变量的方式,他是由实例(C#中用变量类型有些欠妥,用对象实例比较精确点)类型决定的。

    C/C++的 堆与栈区别

    点击打开链接

    点击打开链接


    2. NET FRAMEWORK SDK 文档中已经指明了哪些是引用类型,哪些是值类型

    值类型:值类型称为结构或枚举。struct ,Int32,Boolean,Decimal, 、这些结构都是从System.ValueType派生下来的。所有的枚举都是从System.Enum派生下来的

    引用类型:类 都是引用类型


    3.初始化

    值类型变量创建的时候默认是个包含基础类型的值,所有成员被初始化为 0

    引用类型变量创建的时候默认是null,表明没有指向一个有效的对象,如果直接使用,会抛出一个NullReferenceException的异常。

    展开全文
  • 用Jeffrey Richter(《CLR via C#》作者)的话来说,“不理解引用类型值类型区别的程序员将会把代码引入诡异的陷阱和诸多性能问题”。这就要求我们正确理解和使用值类型引用类型值类型包括C#的基本类型(用...
  • C# 值类型与引用类型 null与可空类型

    千次阅读 2017-10-10 11:05:10
    一,值类型与引用类型值类型就是内存中某处位置保存的值,该值具备实际意义。比如int i=10;内存中的10就是i的值。引用类型也是内存中保存的值,但是该值指向内存中其他位置。比如String str=”123”。str保存的实际...

    点此查看全部文字教程、视频教程、源代码

    1. 值类型与引用类型

    值类型就是内存中某处位置保存的值,该值具备实际意义。比如int i=10;内存中的10就是i的值。

    引用类型也是内存中保存的值,但是该值指向内存中其他位置。比如String str=“123”。str保存的实际上是"123"在内存中存储的位置的地址,只是我们在使用的时候C#语言知道str是引用类型就把对应的"123"取出来给我们用。

    2. null的含义

    值类型总是包含一个值,所以不存在null。引用类型的值可以是null,此时表示该引用类型还未指向具体的空间,也就是还没包含值。

    3. 可空类型

    可控类型是指值可以为null的值类型,有点拗口,例如:

          static void Main(string[] args)
            {
                int? i = null;
                if (i == null)
                    Console.WriteLine("i is null");
            }
    

    定义一个int?类型的i,此处int?表示可空类型。

    展开全文
  • 近期遇到了DateTime到底是值类型还是引用类型的疑惑,顺势较深入地了解一下DateTime相关的内容,大家有需要的朋友可以参考下
  • 在刚参加工作面试时,我们经常会遇到有关值类型引用类型的问题,你回答的怎么样直接影响你在别人心目中的印象,你回答的不好说明你对C#没有深入的了解学习,今天我带大家回顾下C#中的引用类型值类型。...
  • C#中值类型与引用类型的区别

    千次阅读 2018-09-28 12:09:01
    值类型是直接存储一个数值,而...我们知道,在C#中,结构体是值类型,类是引用类型值类型可以减少对堆的管理、使用,减少垃圾回收,表现出更好的性能。但是值类型也有不好的一面,比如会涉及到装箱拆箱等操作。而...
  • c#中数据类型可根据存储方式的不同分为值类型引用类型,也就是标题中的两种 查看方法: 有兴趣可以自己尝试一下。在VS中加入断点(F9 添加,左侧会出现一个小红点),按F10 一行一行运行(左侧箭头所指的为即将...
  • C#详解值类型引用类型区别

    万次阅读 多人点赞 2016-04-20 17:59:42
    C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。 值类型(value type):byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型...
  • 主要介绍了C#值类型引用类型中的Equals和==的区别浅析,本文分别对C#值类型引用类型中的Equals和==做了讲解和给出了实例,需要的朋友可以参考下
  • 举几个值类型引用类型的内存配置: 值类型存储在栈中,引用类型堆里:  1,数组  数组是引用类型,但是数组的元素可以是值类型引用类型    2. 结构  结构是值类型,简略的看个例子  struct ...
  • 这种决定非常重要,用《CLR via C#》作者Jeffrey Richter的话来 说,“不理解引用类型值类型区别的程序员将会给代码引入诡异的bug和性能问题(I believe that a developer who misunderstands the difference ...
  •  我们知道,C#中的每一种类型要么是值类型,要么是引用类型。所以每个对象要么是值类型的实例,要么是引用类型的实例。  值类型引用类型的基类  引用类型值类型都继承自System.Object类。不同的是,几乎...
  • c# 引用类型值类型

    2021-01-01 06:45:25
    CLR支持两种类型:引用类型值类型引用类型总是从托管堆上分配的。 c#中的New操作符返回对象的内存地址。 引用对象的注意点: 1、内存从托管堆中分配 2、堆上分配对象,有一些额外的操作,影响一些性能的 3、从...
  • c#中提供的数据类型有两类,值类型引用类型。 概念上,值类型,直接存储其值;引用类型,存储对其值的引用。 内存空间上,值类型在栈中操作;引用类型在堆中分配存储单元。 其中对于栈和堆的理解参考知乎。 ...
  • 主要介绍了c#字符串值类型与引用类型比较示例,需要的朋友可以参考下
  • c# 值类型实例构造器

    2020-12-25 22:25:35
    引用类型包含值类型字段,引用类型初始化后,值类型默认会被初始化为0、Null。 CLR允许为值类型定义构造器,但是构造器的调用,就必须显式的写代码来调用它们。 CLR不允许为值类型定义无参构造器。只能定义有参构造...
  • 前言:值类型引用类型都是用来存储数据的(值类型存数据,引用类型存引用),但是这两种类型本身又需要存储在内存中。 堆栈出现的原因:在程序运行中,为了提高程序的运行效率,就分出了一大一小两块区域来分别...
  • 主要介绍了一看就懂:图解C#中的值类型引用类型、栈、堆、ref、out,本文用浅显易懂的语言组织介绍了这些容易混淆的概念,需要的朋友可以参考下
  • C#基础】值类型引用类型

    千次阅读 热门讨论 2016-10-23 18:58:18
    在备战软考的过程中,涉及到了原型模式,其中讲到了在浅复制的过程中,值类型引用类型是有区别的。那么究竟什么是值类型,什么是引用类型呢? 在理解这两个概念之前,我们先谈一谈栈和堆。
  • 主要介绍了C#预定义数据类型之值类型引用类型介绍,本文着重讲解了引用类型中的object(对象)类型和string(字符串)类型,需要的朋友可以参考下

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 125,124
精华内容 50,049
关键字:

c#值类型与引用类型

c# 订阅