为什么值类型和引用类型

时间:2012-08-18 17:07:20

标签: c#

我知道b / w值类型和引用类型的区别。但为什么我们需要两者?我们可以使用引用类型而不是值类型。

5 个答案:

答案 0 :(得分:1)

实际上,您可以构建一种只知道引用类型的语言。价值类型主要是出于效率原因。实现数值类型和bool类型更有效,例如,作为值类型。

答案 1 :(得分:1)

值类型直接存储value

例如:

//I and J are both of type int
I = 20;
J = I;

这里,int是一个值类型,这意味着上述语句将在内存中产生两个位置。对于值类型的每个实例,分配单独的内存并将其存储在堆栈中。它提供快速访问,因为值位于堆栈上。

参考类型将reference存储到值中。

例如:

Vector X, Y; //Object is defined. (No memory is allocated.)
X = new Vector(); //Memory is allocated to Object. //(new is responsible for allocating memory.)
X.value = 30; //Initialising value field in a vector class.
Y = X; //Both X and Y points to same memory location. //No memory is created for Y.
Console.writeline(Y.value); //displays 30, as both points to same memory
Y.value = 50;
Console.writeline(X.value); //displays 50.

注意:如果变量是引用,则可以通过将其值设置为null来指示它不引用任何对象。

引用类型存储在Heap上,它提供相对较慢的访问权限,因为值位于堆上。

答案 2 :(得分:1)

要理解值类型,首先必须了解存储位置和引用类型的概念。

对象通常使用字段来表示其状态,而运行代码通常使用变量和参数来表示其状态。这些东西以及阵列插槽统称为"存储位置"。

引用类型的存储位置包含一个对象标识符,或者一个特殊的&#34; null&#34;值。这样的存储位置实际上并不包含对象 - 只是可以用来快速定位它们的标识符。声明为接口类型的存储位置(例如ICollection<T>)包含对已知实现指定接口的对象的引用(或者&#34; null&#34;)。

复制引用类型存储位置只会复制标识符。它对被引物体的物理状态没有任何影响。从概念上讲,这似乎很简单,但它可能很棘手。问题在于,虽然引用类型存储位置在物理上保存了对象标识符,但语义上这样的存储位置可用于捕获对象的以下任何方面:

  1. 其标识(特别是与其他存储位置相关,其中包含对同一对象的引用)
  2. 其不可改变的特征
  3. 其可变特性

当然,物理上,引用类型存储位置保存对象的标识。尽管如此,这些位置通常在语义上用于保存其他信息。例如,考虑一个对象George包含thing类型的字段ICollection<integer>。这样的存储位置将保持对整数集合的引用,这将允许它们被枚举,并且可以允许或不允许添加或移除事物。 George的内容如何影响thing的内容?它可以通过多种方式影响国家。

  1. `George`可能期望`thing`拥有某种不可变集合的身份 - 这些东西总会返回相同的数字集。如果碰巧存在几个包含相同数字的不可变集合,那么`George`的状态将不会受到`thing`标识的那些集合的影响。它的状态只取决于不可变的内容。
  2. `George`可能在`thing`中存储了对它创建的可变列表的引用,并且它不与任何其他对象共享。 `George`希望`thing`所引用的列表能够保存它放在那里的任何内容,而不是别的。在这种情况下,乔治`不会关心它的集合是否被另一个具有相同可变特征的集合所取代。它只是对该集合的可变内容感兴趣。
  3. “乔治”可能并不关心“事物”所引用的集合的内容,但它的工作是为了收益而向该集合中添加内容。其他一些关心内容的对象。在这种情况下,集合的标识是重要的 - “事物”必须持有对同一集合的引用,而该对象期望看到“乔治”添加的内容。
  4. 最后,George可能对可变内容和`thing`引用的对象的身份感兴趣。如果`George`是期望其集合从其他对象接收项目的对象,则此方案将适用。

请注意,在上述所有四种情况中,存储位置的类型完全相同。关于存储位置声明的任何内容都不会表明它的哪些方面很重要。进一步注意,很多复杂性源于这样一个事实,即类引用通常会封装可变状态和身份。如果存储位置没有封装身份概念,事情就会简单得多。

输入值类型。如果有一个存储位置,其类型是值类型结构,如:

struct Point3d
{
  public double X,Y,Z;
  Point3d(double X, double Y, double Z)
  {
    this.X =  X; this.Y = Y; this.Z = Z;
  }
}

该存储位置将保存该结构字段的内容(在本例中为字段doubleX和{{1}中的三个Y值如果一个类有一个该类型的字段,那个字段所代表的状态将是其中保存的三个数字。与类对象不同,其中等价可能依赖于内容,也可能不依赖于内容,结构类型没有这样的歧义。字段结构(有时称为PODS - 普通旧数据结构)如果它们属于同一类型,并且它们的相应字段是等效的,则它们是等效的。

尽管类可以做许多值类型不能做的事情,但这种能力会造成混乱,模糊和复杂。例如,如果有人希望拍摄快照&#34;在阶级状态中,必须知道其领域的哪些方面对其状态很重要。如果类对象Z的状态取决于它拥有引用的对象Larry的可变状态,那么拍摄Mike状态的快照将还需要拍摄Larry状态的快照(并在原始Mike所持有的副本Larry的所有字段或字段中存储对该快照的引用对Larry)的引用。相比之下,复制一个外露字段结构很容易。只需复制其所有字段。

除了一些限制之外,外露场结构可以成为出色的数据持有者:

  1. 结构(无论是暴露字段还是私有字段)可以容纳可变数据量的唯一方法是保存对包含数据的对象的引用,并且永远不会更改。由于结构无法知道其中一个字段是否包含对象的唯一现存引用,因此无法知道是否有任何引用由期望它们不会更改的内容保留。
  2. 虽然结构可以在自己的方法中修改自身,但编译器有时会在结构副本而不是原始副本上执行结构方法。虽然这通常被认为是避免可变结构的原因,但是这样的问题仅影响修改自身的结构。它不会影响将其场暴露于外部修改的结构。

假设一个类的状态取决于5,000个3-d点。如果Mike类型将是一个类,则必须使Point3d类型为不可变类,或者保留现有的5,000个实例的唯一引用。如果Point3d类型是不可变的,那么无论何时希望改变由此指示的状态的任何方面,都必须创建一个全新的Point3d实例并放弃旧实例。如果类型是可变的,那么无论何时希望将坐标从一个点暴露给外部代码,都必须复制Point3dXY值。这两种选择都不太令人愉快。

使Z暴露字段结构消除了这两个问题。由于Point3d只不过是Point3dXY,因此5,000个Z个对象的数组将只包含15,000个数字。将任何Point3d暴露给外界将自动复制三个相关联的号码。如果有人希望改变,例如Point3d的{​​{1}}坐标不会打扰其他坐标,没问题 - 如果Z存储在数组中,可以将<9.}添加到数组插槽4的Z坐标< / p>

  MyArray[4].Z += 9.8;

如果Point存储在其他类型的集合中,事情会更加尴尬,但也不会太糟糕:

  Point3d temp = MyArray[4];
  temp.Z += 9.8;
  MyArray[4] = temp;

Point是一个班级更方便。

答案 3 :(得分:0)

好吧,也许您错过的一个差异是堆栈中的值类型和堆中的引用类型。这会产生很大的性能差异(因为一个是间接访问的指针,另一个是值本身)。

修改 我提到堆和堆栈是错误的,如评论中所示。正确的说法应该是Eric Lippert所说here

“在桌面CLR上的C#的Microsoft实现中,当值是局部变量或临时值时,值类型存储在堆栈中,而不是lambda或匿名方法的封闭局部变量,并且方法体不是迭代器块,并且抖动选择不注册该值。“

答案 4 :(得分:0)

我认为解释它的最简单方法是:

值类型表示不变的值。无论哪个对象1始终为13.14159265359始终为3.14159265359。并且8/18/2012 17:23 UTC表示始终相同的确切时刻。因此,我可以使用这些值创建一千个不同的intDateTime个对象,无论它们在何处以及如何使用它们,它们总是完全相同。

但是,即使规格相同,参考类型也不总是相同。我可以在一个给定的地址建一个房子,然后把计划交给一个朋友,他可以建造另一个房子,规格相同,大小相同,甚至相同的景观和相同大小的院子,但无论你怎么努力制作它们看起来一样,两栋房子仍然永远不会完全一样。