在c#中引用types和值types有什么区别?
几个月前有人问我这个问题,我无法详细解释。 C#中引用types和值types有什么区别?
我知道值types是int
, bool
, float
等,引用types是delegate
, interface
等等。还是这个错误呢?
你能以专业的方式向我解释吗?
你的例子有点奇怪,因为虽然int
, bool
和float
是特定的types,但是接口和委托是各种types的 – 就像struct
和enum
是值types的种类一样。
我在这篇文章中写了一个参考types和值types的解释。 我很乐意扩展任何你觉得困惑的位。
“TL; DR”版本是想想某个特定types的variables/expression式的值是什么。 对于值types,值是信息本身。 对于引用types,该值是可能为空的引用,或者可能是导航到包含该信息的对象的一种方式。
例如,把一个variables想象成一张纸。 它上面可能写着“5”或“false”的值,但是它不可能有我的房子……它必须指向我的房子。 这些方向相当于一个参考。 特别是,两个人可以有不同的纸张,包含相同的方向我的房子 – 如果一个人按照这些方向,把我的房子涂成红色,那么第二个人也会看到这个改变。 如果他们只是在纸上分别拍了我的房子的照片 ,那么一个人在纸上着色就根本不会改变他人的纸张。
值types:
保存一些值不是内存地址
例:
结构
存储:
TL; DR :variables的值被存储在任何被删除的地方。 例如,局部variables存在于堆栈中,但是当在一个类中声明为一个成员时,它驻留在与它声明的类紧密耦合的堆上。
更长 : 因此值types被存储在任何地方声明。 例如:函数中的int
值作为局部variables存储在堆栈中,而在类中声明为int
成员的int
值将被存储在具有声明的类的堆中。值在类上键入的生命types与它所声明的类完全相同,几乎不需要垃圾收集器的工作。 尽pipe如此,我会参考@ JonSkeet的书“ C#深度 ”或他的文章“ .NET中的内存 ”来获得更简洁的补充。
优点:
值types不需要额外的垃圾回收。 它将垃圾收集在一起,并将其中的局部variables清理掉。
缺点:
-
当大量的值传递给一个方法时,接收variables实际上会复制,所以在内存中有两个冗余值。
-
由于class级错过了,所有的逃课都失败了
参考types:
保存一个不值的内存地址
例:
类
存储:
存储在堆上
优点:
-
当你将一个引用variables传递给一个方法,并且它改变它确实改变了原始值,而在值types中,给定variables的副本被采用,并且值被改变。
-
当variables的大小较大时,引用types较好
-
由于类作为引用typesvariables来赋予它们可重用性,从而有利于面向对象编程
缺点:
更多的工作引用时分配和取消引用时读取value.extra重载垃圾收集器
我发现如果你知道计算机如何在内存中分配内容并知道指针是什么,那么理解这两者的区别就容易多了。
引用通常与一个指针相关联。 意思是你的variables所在的内存地址实际上是在不同的内存位置保存了另一个实际对象的内存地址。
我将要提供的例子大大地过于简化了,所以要拿下一点盐。
想象一下,计算机内存是一堆连续的邮政信箱(从邮政信箱0001到邮政信箱n开始),可以容纳一些东西在里面。 如果邮政信箱没有为你做,请尝试一个哈希表或字典或数组或类似的东西。
因此,当你做这样的事情时:
var a =“Hello”;
电脑将执行以下操作:
- (在1000处开始,在1001处),1(在1002处),1(在1003处)和o(在1004处)分配存储器(例如从存储器位置1000开始5个字节)。
- 分配在内存中的某个地方(比如0500),并将其分配给variablesa。
所以这就像一个别名(0500是)。 - 将该内存位置(0500)的值分配给1000(这是stringHello在内存中启动的位置)。 因此,variablesa持有对“Hello”string的实际起始内存位置的引用 。
值types将实际东西保存在其内存位置。
因此,当你做这样的事情时:
var a = 1;
电脑将执行以下操作:
- 在0500分配一个内存位置,并将其分配给variablesa(相同的别名事物)
- 把它的值1(在内存位置0500)。
请注意,我们没有分配额外的内存来保存实际值(1)。 因此a实际上是保持实际值 ,这就是为什么它被称为值types。
这是大约两年前从另外一个论坛发表的一篇文章。 尽pipe语言是vb.net(而不是C#),但是值types与引用types的概念在整个.NET中是一致的,并且这些示例依然成立。
记住在.net中,所有types都是从基类Object派生的。 值types被devise为像这样行为,但最终它们也inheritance了基类Object的function。
A.值types就是这样 – 它们表示存储离散值的内存中的一个独特区域。 值types是固定的内存大小,并存储在堆栈中,这是一个固定大小的地址的集合。
当你做这样的陈述时:
Dim A as Integer DIm B as Integer A = 3 B = A
你已经完成了以下工作:
- 在内存中创build了2个足以容纳32位整数值的空间。
- 在分配给A的内存分配中放置值3
- 在分配给B的存储器分配中放置值3,方法是分配与A中保持的值相同的值。
每个variables的值都存在于每个存储单元中。
B.参考types可以有不同的大小。 因此,它们不能被存储在“堆栈”中(请记住,堆栈是固定大小的内存分配的集合?)。 它们存储在“Managed Heap”中。 指向托pipe堆上的每个项目的指针(或“引用”)都保存在堆栈中(如“地址”)。 您的代码使用堆栈中的这些指针来访问存储在托pipe堆中的对象。 所以当你的代码使用一个引用variables时,它实际上是使用一个指针(或“地址”指向托pipe堆中的内存位置)。
假设您已经创build了一个名为clsPerson的类,并带有一个stringProperty Person.Name
在这种情况下,当你做这样的陈述时:
Dim p1 As clsPerson p1 = New clsPerson p1.Name = "Jim Morrison" Dim p2 As Person p2 = p1
在上面的例子中,p1.Name属性将返回“Jim Morrison”,就像你期望的那样。 p2.Name属性也将返回“Jim Morrison”,就像你直觉地期望的那样。 我相信p1和p2代表堆栈上不同的地址。 但是,现在您已将p2赋值为p1的值,则p1和p2都指向托pipe堆上的相同位置。
现在COnsider这种情况:
Dim p1 As clsPerson Dim p2 As clsPerson p1 = New clsPerson p1.Name = "Jim Morrison" p2 = p1 p2.Name = "Janis Joplin"
在这种情况下,您已在堆栈上使用指向对象的指针p1在受pipe堆上创build了一个新的Person类实例,并再次为该对象实例的Name属性指定了一个值“Jim Morrison”。 接下来,您在堆栈中创build了另一个指针p2,并将其指向托pipe堆上与p1引用的地址相同的地址(当您创build分配p2 = p1时)。
这里是扭曲的。 当您为p2的名称属性赋值“Janis Joplin”时,您将更改p1和p2所引用的对象的名称属性,以便运行以下代码:
MsgBox(P1.Name) 'Will return "Janis Joplin" MsgBox(p2.Name) 'will ALSO return "Janis Joplin"Because both variables (Pointers on the Stack) reference the SAME OBJECT in memory (an Address on the Managed Heap).
这有道理吗?
持续。 如果你这样做:
DIm p1 As New clsPerson Dim p2 As New clsPerson p1.Name = "Jim Morrison" p2.Name = "Janis Joplin"
你现在有两个不同的人物。 但是,一旦你再次做这个:
p2 = p1
你现在已经指出了“吉姆·莫里森”。 (我不太确定p2引用的堆上的对象发生了什么……我认为现在已经超出了范围,这是希望有人能够让我直观的那些领域之一…)。 -EDIT:我相信这就是为什么你要在做新的任务之前设置p2 = Nothing或p2 =新的clsPerson。
再次,如果你现在做这个:
p2.Name = "Jimi Hendrix" MsgBox(p1.Name) MsgBox(p2.Name)
msgBoxes现在将返回“Jimi Hendrix”
这可能会让人困惑,我最后一次说,我可能会有一些细节错误。
祝你好运,并希望其他比我更了解的人会来帮助澄清一些。 。 。
值数据types和参考数据types
1) 值 (直接包含数据)而是引用 (指数据)
2)在价值 (每个variables都有自己的副本),但
在引用 (多于variables可以引用一些对象)
3)在值 (操作variables不能影响其他variables),但在参考 (variables可以影响其他)
4) 值types是(int,bool,float),但引用types是(数组,类对象,string)
“基于值types的variables直接包含值,将一个值typesvariables赋值给另一个复制所包含的值,这不同于引用typesvariables的赋值,它将引用复制到对象而不是对象本身。 来自微软的图书馆。
你可以在这里和这里find更完整的答案。
有时候解释不会对初学者特别有帮助。 您可以将值types想象为数据文件和引用types作为文件的快捷方式。
所以如果你复制一个引用variables,你只能将链接/指针复制到内存中的某个真实数据。 如果你复制一个值types,你真的克隆内存中的数据。
这在神秘的方面可能是错误的,但是为了简单起见:
值types是通过“按值”正常传递的值(如此复制它们)。 引用types是“通过引用”传递的(所以给出一个指向原始值的指针)。 .NET ECMA标准没有保存这些“事物”的保存位置。 您可以构build一个无堆栈的.NET实现,或者一个无需执行的实现(第二个将非常复杂,但是您可能会使用光纤和许多堆栈)
结构是值types(int,bool …是结构体,或者至less被模拟为…),类是引用types。
值types从System.ValueType下降。 引用types从System.Objectinheritance。
现在..最后你有值types,“引用对象”和引用(在C ++中,它们将被称为指向对象的指针,在.NET中它们是不透明的,我们不知道它们是什么。是对象的“手柄”)。 这些最后类似于值types(它们通过复制传递)。 所以一个对象由对象(一个引用types)和零个或多个引用(与值types相似)组成。 当零引用时,GC可能会收集它。
一般情况下(在.NET的“默认”实现中),Valuetypes可以放在堆栈上(如果它们是本地字段)或堆上的(如果它们是类的字段,如果它们是迭代器函数中的variables,如果它们是由闭包引用的variables,如果它们在asynchronous函数中使用variables(使用较新的Async CTP)…)。 引用的值只能去堆。 引用使用与值types相同的规则。
在堆types的情况下,因为它们处于迭代器函数,asynchronous函数或被闭包引用,如果您观察编译的文件,您将看到编译器创build了一个类来放置这些variables,当你调用这个函数的时候,这个类就build立了。
现在,我不知道怎么写很长的东西,而且我有更好的事情要做。 如果你想要一个“精确的”“学术”“正确的”版本,阅读这个:
http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx
这是15分钟我正在找它! 这比msdn版本更好,因为它是一个精简的“随时可用”的文章。
考虑引用types最简单的方法就是把它们当作“对象ID”; 用对象ID唯一可以做的事情就是创build一个,复制一个,查询或者操纵一个对象的types,或者比较两个是否相等。 用对象ID做任何其他事情的尝试将被认为是对由该ID引用的对象进行所指示的动作的简写。
假设我有两个Cartypes的variablesX和Y – 一个引用types。 Y碰巧持有“对象ID#19531”。 如果我说“X = Y”,这将导致X保持“对象ID#19531”。 请注意,X和Y都没有汽车。 汽车,也被称为“对象ID#19531”,被存储在别处。 当我将Y复制到X时,我所做的只是复制ID号码。 现在假设我说X.Color = Colors.Blue。 这样的陈述将被视为一个指令去寻找“对象ID#19531”,并把它涂成蓝色。 请注意,尽pipeX和Y现在指的是一辆蓝色的汽车,而不是一个黄色的汽车,但是这个声明实际上并不影响X或Y,因为两者仍然是指“对象ID#19531”,它仍然是同一辆汽车一直都是。
variablestypes和参考值易于应用,并很好地应用于领域模型,促进开发过程。
要消除“价值types”数量的任何神话,我将评论如何在平台上处理这个问题。 NET中,特别是在C#(CSharp)中调用APIS时,通过引用值,引用,在我们的方法和函数中以及如何正确处理这些值的段落。
阅读此文章 C#中的variablestypes值和引用
假设v
是一个值typesexpression式/variables, r
是一个引用typesexpression式/variables
x = v update(v) //x will not change value. x stores the old value of v x = r update(r) //x now refers to the updated r. x only stored a link to r, //and r can change but the link to it doesn't .
所以,一个值typesvariables存储实际值(5,或“h”)。 一个引用types的variables只存储一个链接到一个隐喻框的值。
简而言之,值types通过引用(内存地址)通过值和引用types传递。
这意味着对被调用方法中的值types参数(forms参数)所做的更改不会反映方法调用的值(实际参数)。
但是在被调用的方法中对引用参数所做的更改将反映对调用方法中声明的variables的更改。
这是一个简短的解释。 请参阅此处以详细了解值types,引用types和值types与引用types。