精华内容
下载资源
问答
  • 用Jeffrey Richter(《CLR via C#》作者)的话来说,“不理解引用类型值类型区别的程序员将会把代码引入诡异的陷阱和诸多性能问题”。这就要求我们正确理解和使用值类型引用类型值类型包括C#的基本类型(用...
  • c# 引用类型值类型

    2021-01-01 06:45:25
    CLR支持两种类型:引用类型值类型引用类型总是从托管堆上分配的。 c#中的New操作符返回对象的内存地址。 引用对象的注意点: 1、内存从托管堆中分配 2、堆上分配对象,有一些额外的操作,影响一些性能的 3、从...
  • 近期遇到了DateTime到底是值类型还是引用类型的疑惑,顺势较深入地了解一下DateTime相关的内容,大家有需要的朋友可以参考下
  • 主要介绍了C#引用类型值类型,有需要的朋友可以参考一下
  • 今天小编就为大家分享一篇关于C#引用类型值类型的适用场合和区别,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • C#值类型与引用类型区别,和一些基础算法,刚入门的小伙伴,希望对你有用
  • 在刚参加工作面试时,我们经常会遇到有关值类型引用类型的问题,你回答的怎么样直接影响你在别人心目中的印象,你回答的不好说明你对C#没有深入的了解学习,今天我带大家回顾下C#中的引用类型值类型。...
  • 举几个值类型引用类型的内存配置: 值类型存储在栈中,引用类型堆里:  1,数组  数组是引用类型,但是数组的元素可以是值类型引用类型    2. 结构  结构是值类型,简略的看个例子  struct ...
  • 主要介绍了C#预定义数据类型之值类型引用类型介绍,本文着重讲解了引用类型中的object(对象)类型和string(字符串)类型,需要的朋友可以参考下
  • 主要介绍了C#值类型引用类型中的Equals和==的区别浅析,本文分别对C#值类型引用类型中的Equals和==做了讲解和给出了实例,需要的朋友可以参考下
  • 主要介绍了一看就懂:图解C#中的值类型引用类型、栈、堆、ref、out,本文用浅显易懂的语言组织介绍了这些容易混淆的概念,需要的朋友可以参考下
  •  我们知道,C#中的每一种类型要么是值类型,要么是引用类型。所以每个对象要么是值类型的实例,要么是引用类型的实例。  值类型引用类型的基类  引用类型值类型都继承自System.Object类。不同的是,几乎...
  • 主要介绍了c#字符串值类型与引用类型比较示例,需要的朋友可以参考下
  • C#详解值类型引用类型区别

    万次阅读 多人点赞 2016-04-20 17:59:42
    C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。 值类型(value type):byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型...

    首先,什么是值类型,什么是引用类型?

    在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。

    值类型(value type):byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。值类型变量声明后,不管是否已经赋值,编译器为其分配内存。


            引用类型(reference type):string 和 class统称为引用类型。当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。


    值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。引用类型的对象总是在进程堆中分配(动态分配)。

            下面的例子说明值类型和引用类型的区别,代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Parameters
    {
        class Program
        {
            static void Main(string[] args)
            {
                Dowork();
            }
    
            static void Dowork()
            {
                int i = 0;  // int 是值类型
                Console.WriteLine(i);   // i = 0
                Pass.value(i);          // 值类型使用的是 i 的副本,i不变
                Console.WriteLine(i);   // i = 0
    
                WrappendInt wi = new WrappendInt(); // 创建类 WrappendInt 的另外一个实例
                Console.WriteLine(wi.Number);   // 0 // 被默认构造器初始化为 0
                Pass.Reference(wi);     // 调用方法,wi 和 param 将引用同一个对象
                Console.WriteLine(wi.Number);   // 42
            }
        }
    
        class Pass
        {
            public static void value(int param)
            {
                param = 42; // 赋值操作使用的是值类型参数的一个副本,原始参数不受影响
            }
    
            public static void Reference(WrappendInt param) // 创建类 WrappendInt 的一个实例
            {
                param.Number = 42;  // 此参数是引用类型的参数
            }
        }
    
        class WrappendInt   // 类是引用类型
        {
            public int Number;
        }
    }

    输出结果为:

    0

    0

    0

    42


    从概念上看,值类型直接存储其值,而引用类型存储对其值的引用。这两种类型存储在内存的不同地方。在C#中,我们必须在设计类型的时候就决定类型实例的行为。这种决定非常重要,用《CLR via C#》作者Jeffrey Richter的话来说,“不理解引用类型和值类型区别的程序员将会给代码引入诡异的bug和性能问题(I believe that a developer who misunderstands the difference between reference types and value types will introduce subtle bugs and performance issues into their code.)”。这就要求我们正确理解和使用值类型和引用类型。

    1. 通用类型系统

    C#中,变量是值还是引用仅取决于其数据类型。

    C#的基本数据类型都以平台无关的方式来定义。C#的预定义类型并没有内置于语言中,而是内置于.NET Framework中。.NET使用通用类型系统(CTS)定义了可以在中间语言(IL)中使用的预定义数据类型,所有面向.NET的语言都最终被编译为IL,即编译为基于CTS类型的代码。

    例如,在C#中声明一个int变量时,声明的实际上是CTS中System.Int32的一个实例。这具有重要的意义:

    • 确保IL上的强制类型安全;
    • 实现了不同.NET语言的互操作性;
    • 所有的数据类型都是对象。它们可以有方法,属性,等。例如:
    int  i;
    i
     = 1
    ;
    string
      s;
    s
     = i.ToString();

    MSDN的这张图说明了CTS中各个类型是如何相关的。注意,类型的实例可以只是值类型或自描述类型,即使这些类型有子类别也是如此。

    c#中引用类型和值类型的区别

    2. 值类型

    C#的所有值类型均隐式派生自System.ValueType:

    • 结构体:struct(直接派生于System.ValueType);
      • 数值类型:
        • 整型:sbyte(System.SByte的别名),short(System.Int16),int(System.Int32),long(System.Int64),byte(System.Byte),ushort(System.UInt16),uint(System.UInt32),ulong(System.UInt64),char(System.Char);
        • 浮点型:float(System.Single),double(System.Double);
        • 用于财务计算的高精度decimal型:decimal(System.Decimal)。
      • bool型:bool(System.Boolean的别名);
      • 用户定义的结构体(派生于System.ValueType)。
    • 枚举:enum(派生于System.Enum);
    • 可空类型(派生于System.Nullable<T>泛型结构体,T?实际上是System.Nullable<T>的别名)。

    每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。例如:

    int i = new int();

    等价于:

    Int32 i = new Int32();

    等价于:

    int i = 0;

    等价于:

    Int32 i = 0;

    使用new运算符时,将调用特定类型的默认构造函数并对变量赋以默认值。在上例中,默认构造函数将值0赋给了i。MSDN上有完整的默认值表

    所有的值类型都是密封(seal)的,所以无法派生出新的值类型。

    值得注意的是,引 用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

    可以用Type.IsValueType属性来判断一个类型是否为值类型:

    TestType testType = new  TestType ();
    if
      (testTypetype.GetType().IsValueType)
    {
         Console.WriteLine(
    "{0} is value type."
    , testType.ToString());
    }

    3. 引用类型

    C#有以下一些引用类型:

    • 数组(派生于System.Array)
    • 用户用定义的以下类型:
      • 类:class(派生于System.Object);
      • 接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。Anders在《C# Programming Language》中说,接口只是表示一种约定[contract]);
      • 委托:delegate(派生于System.Delegate)。
    • object(System.Object的别名);
    • 字符串:string(System.String的别名)。

    可以看出:

    • 引用类型与值类型相同的是,结构体也可以实现接口;
    • 引用类型可以派生出新的类型,而值类型不能;
    • 引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型);
    • 引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。

    对于最后一条,经常混淆的是string。我曾经在一本书的一个早期版本上看到String变量比string变量效率高;我还经常听说String是引用类型,string是值类型,等等。例如:

    string s1 = "Hello, " ;
    string s2 = "world!"
    ;
    string s3 = s1 + s2;//s3 is "Hello, world!"

    这确实看起来像一个值类型的赋值。再如:

    string s1 = "a" ;
    string s2 = s1
    ;
    s1
     = "b";//s2 is still "a"

    改变s1的值对s2没有影响。这更使string看起来像值类型。实际上,这是运算符重载的结果,当s1被改变时,.NET在托管堆上为s1重新分配了内存。这样的目的,是为了将做为引用类型的string实现为通常语义下的字符串。

    4. 值类型和引用类型在内存中的部署

    经常听说,并且经常在书上看到:值类型部署在栈上,引用类型部署在托管堆上。实际上并没有这么简单。

    MSDN上说:托管堆上部署了所有引用类型。这很容易理解。当创建一个应用类型变量时:

    object reference = new object();

    关键字new将在托管堆上分配内存空间,并返回一个该内存空间的地址。左边的reference位于栈上,是一个引用,存储着一个内存地址;而这个地址指向的内存(位于托管堆)里存储着其内容(一个System.Object的实例)。下面为了方便,简称引用类型部署在托管推上。

    再来看值类型。《C#语言规范》上的措辞是“结构体不要求在堆上分配内存(However, unlike classes, structs are value types and do not require heap allocation)”而不是“结构体在栈上分配内存”。这不免容易让人感到困惑:值类型究竟部署在什么地方?

    4.1 数组

    考虑数组:

    int[] reference = new int[100];

    根据定义,数组都是引用类型,所以int数组当然是引用类型(即reference.GetType().IsValueType为false)。

    而int数组的元素都是int,根据定义,int是值类型(即reference[i].GetType().IsValueType为true)。那么引用类型数组中的值类型元素究竟位于栈还是堆?

    如果用WinDbg去看reference[i]在内存中的具体位置,就会发现它们并不在栈上,而是在托管堆上。

    实际上,对于数组:

    TestType[] testTypes = new TestType[100];

    如果TestType是值类型,则会一次在托管堆上为100个值类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。

    如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。

    4.2 类型嵌套

    更容易让人困惑的是引用类型包含值类型,以及值类型包含引用类型的情况:

    public class  ReferenceTypeClass
    {
        
    private int
      _valueTypeField;
        
    public
      ReferenceTypeClass()
         {
             _valueTypeField
     = 0
    ;
         }
        
    public void
      Method()
         {
            
    int valueTypeLocalVariable = 0
    ;
         }
    }
    ReferenceTypeClass referenceTypeClassInstance
     = new ReferenceTypeClass();//Where is _valueTypeField?

    referenceTypeClassInstance.Method();//Where is valueTypeLocalVariable?

    public struct  ValueTypeStruct
    {
        
    private object
      _referenceTypeField;
        
    public void
      Method()
         {
             _referenceTypeField
     = new object
    ();
            
    object referenceTypeLocalVariable = new object
    ();
         }
    }
    ValueTypeStruct valueTypeStructInstance
     = new
      ValueTypeStruct();
    valueTypeStructInstance.Method();
    //Where is _referenceTypeField?And where is referenceTypeLocalVariable?

    单看valueTypeStructInstance,这是一个结构体实例,感觉似乎是整块扔到栈上的。但是字段_referenceTypeField是引用类型,局部变量referenceTypeLocalVarible也是引用类型。

    referenceTypeClassInstance也有同样的问题,referenceTypeClassInstance本身是引用类型,似乎应该整块部署在托管堆上。但字段_valueTypeField是值类型,局部变量valueTypeLocalVariable也是值类型,它们究竟是在栈上还是在托管堆上?

    规律是:

    • 引用类型部署在托管堆上;
    • 值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。

    我们来分析一下上面的代码。对于引用类型实例,即referenceTypeClassInstance:

    • 从上下文看,referenceTypeClassInstance是一个局部变量,所以部署在托管堆上,并被栈上的一个引用所持有;
    • 值类型字段_valueTypeField属于引用类型实例referenceTypeClassInstance的一部分,所以跟随引用类型实例referenceTypeClassInstance部署在托管堆上(有点类似于数组的情形);
    • valueTypeLocalVariable是值类型局部变量,所以部署在栈上。

    而对于值类型实例,即valueTypeStruct:

    • 根据上下文,值类型实例valueTypeStructInstance本身是一个局部变量而不是字段,所以位于栈上;
    • 其引用类型字段_referenceTypeField不存在跟随的问题,必然部署在托管堆上,并被一个引用所持有(该引用是valueTypeStruct的一部分,位于栈);
    • 其引用类型局部变量referenceTypeLocalVariable显然部署在托管堆上,并被一个位于栈的引用所持有。

    所以,简单地说“值类型存储在栈上,引用类型存储在托管堆上”是不对的。必须具体情况具体分析。


    5. 辨明值类型和引用类型的使用场合


    在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:
    SomeType[] oneTypes = new SomeType[100];
    如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。
    一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:
    该类型的主要职责用于数据存储。 
    该类型的共有接口完全由一些数据成员存取属性定义。 
    该类型永远不可能有子类。 
    该类型不具有多态行为。


    5. 值类型和引用类型的区别(小结)


    相同点:
    引用类型可以实现接口,值类型当中的结构体也可以实现接口;
    引用类型和值类型都继承自System.Object类。

    1)范围方面
    C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。
    C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。

    2)内存分配方面:

    数组的元素不管是引用类型还是值类型,都存储在托管堆上。

    引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引用类型部署在托管推上。而值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实 例)存储;作为局部变量时,存储在栈上。(栈的内存是自动释放的,堆内存是.NET中会由GC来自动释放)

    3)适用场合

    值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。

    • 引用类型可以派生出新的类型,而值类型不能,因为所有的值类型都是密封(seal)的;
    • 引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型,如   int? a = null;  );
    • 引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。


    值得注意的是,引 用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即 直接继承System.ValueType。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。




    展开全文
  • 这种决定非常重要,用《CLR via C#》作者Jeffrey Richter的话来 说,“不理解引用类型值类型区别的程序员将会给代码引入诡异的bug和性能问题(I believe that a developer who misunderstands the difference ...
  • c# 值类型实例构造器

    2020-12-25 22:25:35
    引用类型包含值类型字段,引用类型初始化后,值类型默认会被初始化为0、Null。 CLR允许为值类型定义构造器,但是构造器的调用,就必须显式的写代码来调用它们。 CLR不允许为值类型定义无参构造器。只能定义有参构造...
  • C#引用类型值类型 包含了C#中的所有引用类型值类型的分类,别对于各个类型的关键字,给出来相应的例子。值得参考。
  • C#中值类型与引用类型的区别

    千次阅读 2018-09-28 12:09:01
    值类型是直接存储一个数值,而...我们知道,在C#中,结构体是值类型,类是引用类型值类型可以减少对堆的管理、使用,减少垃圾回收,表现出更好的性能。但是值类型也有不好的一面,比如会涉及到装箱拆箱等操作。而...

    值类型是直接存储一个数值,而引用类型是存储对值的引用,这两种类型分别存储在不用的内存区域。而从内存上看,值类型是在栈中的操作,而引用类型是在堆中的操作。值类型是具体的那个数值所占用的空间大小,而引用类型是存放那个数值的空间地址。
    我们知道,在C#中,结构体是值类型,类是引用类型。值类型可以减少对堆的管理、使用,减少垃圾回收,表现出更好的性能。但是值类型也有不好的一面,比如会涉及到装箱拆箱等操作。而C#中的委托(Delegate)是一种引用类型,该引用类型与其它引用类型有所不同,在委托对象的引用中存放的不是对数据的引用,而是存放对方法的引用,即在委托的内部包含一个指向某个方法的指针。通过使用委托把方法的引用封装在委托对象中,然后将委托对象传递给调用引用方法的代码。

    通过代码说明如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace Stock
    {
        class Program
        {
            static void Main(string[] args)
            {
                Refer.show();    //静态方法不需要实例化
            }
        }
        public class stamp  //定义一个stamp类
        {
            public string Name { get; set; }   //作为引用类型
            public int age { get; set; }   //作为值类型
        }
        public static class Refer   //定义静态类
        {
            public static void show()   //定义静态方法
            {
                stamp stamp1 = new stamp { Name = "xiaoming", age = 22 };
                stamp stamp2 = new stamp { Name = "xiaoqing", age = 23 };
                int age = stamp1.age;
                stamp1.age = 34;
                stamp stamp3 = stamp2;  //引用类型
                stamp2.Name = "xaiohuang";
                Console.WriteLine("stamp1's age : {0}", stamp1.age);
                Console.WriteLine("Current age : {0}", age);
                Console.WriteLine("stamp2's Name : {0}", stamp2.Name);
                Console.WriteLine("stamp3's Name : {0}", stamp3.Name);
                Console.ReadLine();
            }
        }
    }
    

    运行结果如下:

    运行结果分析:可以看到,当改变Stamp1.age的值时,变量age的值没跟着改变。而在改变了stamp2.Name的值的时候,stamp2.Name的值跟着改变了。这是因为引用类型只是包含堆上面数据区域中地址的引用,他们都指向了同一块内存区域。所以当stamp3引用stamp2时(stamp stamp3 = stamp2;),stamp2改变的值是能够影响stamp3的。当然,改变stamp3的值也能够影响到stamp2。

    展开全文
  • 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?表示可空类型。

    展开全文
  • C#只有两种数据类型:值类型引用类型 值类型在线程栈分配空间,引用类型在托管堆分配空间 值类型转为引用类型称成为装箱,引用类型转为值类型称为拆箱 以下是值类型引用类型对照表 从上图可以简单看出:string...
  • 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接口,使得他们可以比较大小
    展开全文
  • C#中,值类型引用类型是相当重要的两个概念,必须在设计类型的时候就决定类型实例的行为。如果在编写代码时不能理解引用类型值类型的区别,那么将会给代码带来不必要的异常。很多人就是因为没有弄清楚这两个...
  • 今天要写的东西都是书中一些概念性的东西,就当抄笔记,以提问对话的方式将其写出来吧,说不定以后面试能有点谈资~~~  Q1.C#1系统类型包含哪三点特性?  A1.C#1类型系统是静态...值类型引用类型,分别说下类、结构
  • 主要给大家介绍了关于c#基础系列之值类型引用类型的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

空空如也

空空如也

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

c#引用类型与值类型

c# 订阅