C # value and reference types of the difference between two types of C # data types: value type and reference type

Reprint: training of stone 

C # classes, according to two types: value types and reference types

 

What is a value type, what is a reference type

 

The concept: The value type stores its value directly, and reference types store references to their values. Deployment: the managed heap deployed all reference types.

 

Reference types: a base class for Objcet

Value type: all implicitly derived from System.ValueType:

 

Value type:

byte, short, int, long, float, double, decimal, char, bool and collectively referred to as a value type struct.

Reference types:

string and class collectively referred to as a reference type.

 

  • After the value of the type variable declarations, regardless of whether the assignment, the compiler allocate memory for.
  • When a reference type declaration class, allocated only a small piece of the stack for receiving a memory address, but this time not assigned the heap memory. When creating new new instance of a class used, the space allocated on the heap and save the heap address space into the die space allocated on the stack.
  • Examples of value type is generally assigned (static allocation) on the thread stack, but in some cases may be stored in a heap.
  • Object reference types are always allocated on the heap (dynamically allocated) in the process.

We look at the following piece of code:

Output:

 

Value Type allocated space in the stack varies due to the type of variable;

Reference the same type of space within the stack;

 

1.  Common Type System

C #, or reference variable is a value which only depends on the data type.

C # basic data types in a platform-independent way to define. C # predefined types are not built into the language, but built on the .NET Framework. Use .NET Common Type System (CTS) defines predefined data types may be used in an intermediate language (IL), all for .NET languages ​​are compiled into a final IL, i.e. CTS type of code is compiled based.

For example, to declare a variable int in C #, the statement is actually an instance of the CTS System.Int32. This is of great significance:

  • To ensure that the mandatory safety on IL;
  • Achieve interoperability of different .NET languages;
  • All data types are objects. They can have methods, properties, and the like. E.g:

int i;

i = 1;

string s;

s = i.ToString();

 

以下关系图(来自MSDN)说明了这几种类型是如何相关的。注意,类型的实例可以只是值类型或自描述类型,即使这些类型有子类别也是如此。

类型类别:

 

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;

引用类型和值类型都继承自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类型嵌套

 

引用类型部署在托管堆上;

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

从上下文看,mc是一个局部变量,所以部署在托管堆上,并被栈上的一个引用所持有;

值类型字段_value1属于引用类型实例mc的一部分,所以跟随引用类型实例mc部署在托管堆上(有点类似于数组的情形);

value2是值类型局部变量,所以部署在栈上。

而对于值类型实例,即MyStruct

根据上下文,值类型实例ms本身是一个局部变量而不是字段,所以位于栈上;

其引用类型字段_object1不存在跟随的问题,必然部署在托管堆上,并被一个引用所持有(该引用是ms的一部分,位于栈);

其引用类型局部变量_object2显然部署在托管堆上,并被一个位于栈的引用所持有。

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

 

 

C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:

SomeType[] oneTypes = new SomeType[100];

如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。

一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:

该类型的主要职责用于数据存储。

该类型的共有接口完全由一些数据成员存取属性定义。

该类型永远不可能有子类。

该类型不具有多态行为。

 

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

在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:

SomeType[] oneTypes = new SomeType[100];

如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。

一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:

该类型的主要职责用于数据存储。

该类型的共有接口完全由一些数据成员存取属性定义。

该类型永远不可能有子类。

该类型不具有多态行为。

 

 

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

相同点:

引用类型可以实现接口,值类型当中的结构体也可以实现接口;

引用类型和值类型都继承自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()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

什么是值类型,什么是引用类型

 

概念:值类型直接存储其值,而引用类型存储对其值的引用。部署:托管堆上部署了所有引用类型。

 

引用类型:基类为Objcet

值类型:均隐式派生自System.ValueType:

 

值类型:

byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。

引用类型:

string 和 class统称为引用类型。

 

  • 值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
  • 引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
  • 值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。
  • 引用类型的对象总是在进程堆中分配(动态分配)。

我们来看下面一段代码:

输出结果:

 

值类型在栈内分配空间大小因变量类型而异;

引用类型在栈内的空间大小相同;

 

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)说明了这几种类型是如何相关的。注意,类型的实例可以只是值类型或自描述类型,即使这些类型有子类别也是如此。

类型类别:

 

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;

引用类型和值类型都继承自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类型嵌套

 

引用类型部署在托管堆上;

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

从上下文看,mc是一个局部变量,所以部署在托管堆上,并被栈上的一个引用所持有;

值类型字段_value1属于引用类型实例mc的一部分,所以跟随引用类型实例mc部署在托管堆上(有点类似于数组的情形);

value2是值类型局部变量,所以部署在栈上。

而对于值类型实例,即MyStruct

根据上下文,值类型实例ms本身是一个局部变量而不是字段,所以位于栈上;

其引用类型字段_object1不存在跟随的问题,必然部署在托管堆上,并被一个引用所持有(该引用是ms的一部分,位于栈);

其引用类型局部变量_object2显然部署在托管堆上,并被一个位于栈的引用所持有。

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

 

 

C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:

SomeType[] oneTypes = new SomeType[100];

如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。

一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:

该类型的主要职责用于数据存储。

该类型的共有接口完全由一些数据成员存取属性定义。

该类型永远不可能有子类。

该类型不具有多态行为。

 

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

在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:

SomeType[] oneTypes = new SomeType[100];

如 果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后 数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主 要是存储数据,值类型比较合适。

一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:

该类型的主要职责用于数据存储。

该类型的共有接口完全由一些数据成员存取属性定义。

该类型永远不可能有子类。

该类型不具有多态行为。

 

 

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

相同点:

引用类型可以实现接口,值类型当中的结构体也可以实现接口;

引用类型和值类型都继承自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()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

Guess you like

Origin www.cnblogs.com/zwj-199306231519/p/11589480.html