何时为不可变类型使用值和引用类型? (。净)

时间:2011-05-12 00:06:02

标签: c# .net immutability

对于可变类型,值和引用类型之间的行为差​​异很明显:

// Mutable value type
PointMutStruct pms1 = new PointMutStruct(1, 2);
PointMutStruct pms2 = pms1;
// pms1 == (1, 2); pms2 == (1, 2);
pms2.X = 3;
MutateState(pms1); // Changes the X property to 4.
// pms1 == (1, 2); pms2 == (3, 2);

// Mutable reference type
PointMutClass pmc1 = new PointMutClass(1, 2);
PointMutClass pmc2 = pmc1;
// pmc1 == (1, 2); pmc2 == (1, 2);
pmc2.X = 3;
MutateState(pmc1); // Changes the X property to 4.
// pmc1 == (4, 2); pmc2 == (4, 2);

然而,对于不可变类型,这种差异不那么明确:

// Immutable value type
PointImmStruct pis1 = new PointImmStruct(1, 2);
PointImmStruct pis2 = pis1;
// pis1 == (1, 2); pis2 == (1, 2);
pis2 = new PointImmStruct(3, pis2.Y);
// Can't mutate pis1
// pis1 == (1, 2); pis2 == (3, 2);

// Immutable reference type
PointImmClass pic1 = new PointImmClass(1, 2);
PointImmClass pic2 = pic1;
// pic1 == (1, 2); pic2 == (1, 2);
pic2 = new PointImmClass(3, pic2.Y);
// Can't mutate pic1 either
// pic1 == (1, 2); pic2 == (3, 2);

不可变引用类型通常也使用值语义(例如规范示例System.String):

string s1 = GenerateTestString(); // Generate identical non-interned strings
string s2 = GenerateTestString(); // by dynamically creating them
// object.ReferenceEquals(strA, strB)) == false;
// strA.Equals(strB) == true
// strA == strB

Eric Lippert之前在他的博客上讨论过(例如here),在堆栈上分配的值类型经常(对于这个讨论并不重要)的事实是一个实现细节,它不应该通常决定是否将对象设为值或引用类型。

鉴于不可变类型行为的这种模糊区别,这使我们有什么标准来决定是否将不可变类型作为引用类型或值类型?

此外,由于对值和变量的不断强调,不可变类型是否总是实现值语义?

3 个答案:

答案 0 :(得分:6)

我会说Eric链接的博客文章给出了答案:

  

我很遗憾文档的确如此   不要专注于最相关的事情;通过   专注于一个基本上无关紧要的   实施细节,我们放大了   这种实施的重要性   细节和模糊的重要性   是什么使语义值类型   有用。我非常希望所有这些   解释什么是“堆栈”的文章   反而会花时间解释   什么是“按价值复制”的意思   以及如何误解或误用   “按值复制”可能会导致错误。

如果您的对象应具有“按值复制”语义,则将它们设为值类型。如果他们应该具有“引用复制”语义,请将它们作为引用类型。

他也说过这个,我同意:

  

我总是选择价值   类型与引用类型基于   该类型是否是语义上的   表示值或语义a   引用某事。

答案 1 :(得分:2)

有一个重要类别的不可变类型(Eric Lippert也在某种程度上写过)必须实现为崇敬类型:递归类型,如列表节点,树节点等。值类型不能具有循环定义,例如,链表节点可以:

class IntNode
{
    private readonly int value;
    private readonly IntNode next;
}

答案 2 :(得分:1)

.NET提示String类对此的回答。它是不可变的,但它是一个引用类型。尽可能使您的不可变类型 act 像值类型一样。它是否真的是一种值类型并不重要。

所以我能想到的唯一标准是:如果复制它会很昂贵(String可能涉及大量复制!),请将其作为参考类型。如果可以快速复制,请选择值类型。还要考虑是否需要比较引用 - 这可能是使用不可变引用类型的唯一棘手的部分。