C# 何时对不可变类型使用值和引用类型?(.NET)

C# 何时对不可变类型使用值和引用类型?(.NET),c#,.net,immutability,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 ==

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

// 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
):

Eric Lippert之前在他的博客上讨论过(例如)值类型通常(在本次讨论中并不重要)分配到堆栈上这一事实是一个实现细节,它通常不应该规定对象是值类型还是引用类型

考虑到不可变类型在行为上的这种模糊区分,这给我们留下了什么标准来决定是将不可变类型作为引用类型还是值类型


另外,由于不可变强调值与变量,不可变类型是否应该始终实现值语义?

.NET使用
字符串
类给出了相应的提示。它是不可变的,但是引用类型。使您的不可变类型尽可能像值类型一样工作。它是否真的是值类型并不重要


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

< P>我会说埃里克的博客链接你给了你正确的答案:

我很遗憾,文件没有 不关注最相关的东西;通过 关注一个基本上不相关的问题 在实现细节上,我们放大了 这一执行的重要性 细节和模糊的重要性 值类型在语义上是什么 有用。我衷心希望所有这些 解释什么是“堆栈”的文章 而是会花时间解释 “按价值复制”的确切含义是什么 如何理解或误用 “按值复制”可能会导致错误

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

他也这样说,我同意:

我总是选择价值 类型与基于的引用类型 类型是否在语义上是 在语义上表示一个值或一个符号 提及某事


有一类重要的不可变类型(Eric Lippert也详细介绍过)必须实现为尊重类型:递归类型,如列表节点、树节点等。值类型不能具有循环定义,例如,链表节点具有:

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

值类型始终存储在表示它们的变量或字段中,无论该变量或字段存储在何处。引用类型对象从未实际存储在表示它们的变量或字段中;它们总是存储在堆的其他位置。如果某个类的对象Foo1包含某个引用类型的非空字段Bar1,那么Foo1和Bar1是独立的堆对象。如果我有一个类的对象Foo2,其中包含一个值类型的Bar2,那么Bar2将存储在堆对象Foo2中。@supercat这个问题围绕一篇博客文章展开,文章认为使用运行时的实现细节不应该是是否使用值类型的决定因素,而应该是“按值复制”值类型的语义优于引用类型的“按引用复制”语义。我真的不确定你的评论是在添加什么,除了强调这篇文章建议程序员避免的实现细节。如果类型是不可变的,那么复制引用和复制值在语义上是相同的。除非不可变类型需要与其他类(当然,这是一种有用的模式)具有继承关系,否则不可变值类型和不可变类类型之间的主要区别将与性能有关。主要问题应该是,是否最好为该类型的每个不同实例创建一个单独的heap对象,但可能共享已知相同的实例,或者避免创建堆对象来保存实例。我个人认为,对可变值类型的蔑视很大程度上是由于.net在处理它们时的一些限制和怪癖造成的不幸结果,在许多情况下,实现最佳语义和性能的方法是通过引用传递可变值类型。@supercat我认为我真的不理解您的观点之间的关系。甚至可以在堆栈上创建值类型的全部原因是它们的值复制语义。(可以保证值对象永远不会泄漏到堆栈中。)也就是说,我认为这篇文章已经讨论过了,甚至不能保证在堆栈上分配值对象。唯一应该向程序员公开的是创建的类型是按值复制还是按引用复制。其他一切都应该被VM认为是程序员不透明的运行时优化。
class IntNode
{
    private readonly int value;
    private readonly IntNode next;
}