c# - 根据定义,值类型是不可变的吗?

标签 c# .net immutability value-type

我经常读到 struct s 应该是不可变的 - 它们不是根据定义吗?

你考虑吗int是不可变的?

int i = 0;
i = i + 123;

看起来不错 - 我们得到了一个新的 int并将其分配回 i .那这个呢?
i++;

好的,我们可以把它看作是一条捷径。
i = i + 1;
struct呢? Point ?
Point p = new Point(1, 2);
p.Offset(3, 4);

这真的改变了观点(1, 2) ?我们不应该把它看作是以下 Point.Offset() 的快捷方式吗?返回一个新点?
p = p.Offset(3, 4);

这个想法的背景是这样的——一个没有身份的值类型怎么可能是可变的?您必须至少查看两次以确定它是否已更改。但是没有身份你怎么能做到这一点呢?

我不想通过考虑 ref 来使推理复杂化参数和装箱。我也知道 p = p.Offset(3, 4);p.Offset(3, 4); 更好地表达不变性做。但问题仍然存在——根据定义,值类型不是不可变的吗?

更新

我认为至少涉及两个概念 - 变量或字段的可变性以及变量值的可变性。
public class Foo
{
    private Point point;
    private readonly Point readOnlyPoint;

    public Foo()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2);
    }

    public void Bar()
    {
        this.point = new Point(1, 2);
        this.readOnlyPoint = new Point(1, 2); // Does not compile.

        this.point.Offset(3, 4); // Is now (4, 6).
        this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).
    }
}

在示例中,我们必须字段 - 一个可变的和一个不可变的。因为值类型字段包含整个值,所以存储在不可变字段中的值类型也必须是不可变的。我仍然对结果感到非常惊讶 - 我没想到 readonly 字段保持不变。

变量(除了常量)总是可变的,因此它们意味着对值类型的可变性没有限制。

答案似乎不是那么直接,所以我将重新表述这个问题。

鉴于以下。
public struct Foo
{
    public void DoStuff(whatEverArgumentsYouLike)
    {
        // Do what ever you like to do.
    }

    // Put in everything you like - fields, constants, methods, properties ...
}

能不能给个完整版的Foo和一个使用示例 - 可能包括 ref参数和装箱 - 这样就不可能重写所有出现的
foo.DoStuff(whatEverArgumentsYouLike);


foo = foo.DoStuff(whatEverArgumentsYouLike);

最佳答案

An object is immutable if its state doesn’t change once the object has been created.


简短回答:不,根据定义,值类型不是不可变的。 结构体和类都可以是可变的或不可变的。 所有四种组合都是可能的。如果结构或类具有非只读公共(public)字段、带有 setter 的公共(public)属性或设置私有(private)字段的方法,则它是可变的,因为您可以在不创建该类型的新实例的情况下更改其状态。

长答案:首先,不变性问题仅适用于具有字段或属性的结构或类。最基本的类型(数字、字符串和 null)本质上是不可变的,因为它们没有任何(字段/属性)可以改变。 A 5 is a 5 is a 5. 对 5 的任何操作只会返回另一个不可变值。
您可以创建可变结构,例如 System.Drawing.Point .两者 XY具有修改结构字段的 setter :
Point p = new Point(0, 0);
p.X = 5;
// we modify the struct through property setter X
// still the same Point instance, but its state has changed
// it's property X is now 5
有些人似乎将不变性与值类型通过值(因此它们的名称)而不是通过引用传递这一事实混淆。
void Main()
{
    Point p1 = new Point(0, 0);
    SetX(p1, 5);
    Console.WriteLine(p1.ToString());
}

void SetX(Point p2, int value)
{
    p2.X = value;
}
在这种情况下 Console.WriteLine()写“{X=0,Y=0}”。这里p1未被修改,因为 SetX()修改 p2这是 p1 的副本.这是因为 p1是值类型,不是因为它是不可变的(不是)。
为什么值类型应该是不可变的?原因很多……见this question .主要是因为可变值类型会导致各种不那么明显的错误。在上面的例子中,程序员可能已经预料到 p1成为 (5, 0)调用后SetX() .或者想象一下按稍后可以更改的值进行排序。那么您的排序集合将不再按预期排序。字典和散列也是如此。 Fabulous Eric Lippert ( blog ) 写了一个 whole series about immutability以及为什么他认为这是 C# 的 future 。 Here's one of his examples这使您可以“修改”只读变量。

更新:你的例子:
this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).
正是 Lippert 在他的关于修改只读变量的帖子中提到的内容。 Offset(3,4)居然修改了一个Point ,但它是 readOnlyPoint 的副本,并且它从未分配给任何东西,所以它丢失了。
这就是为什么可变值类型是邪恶的:它们让您认为您正在修改某些东西,而有时您实际上是在修改副本,这会导致意外错误。如 Point是不可变的,Offset()将不得不返回一个新的 Point ,您将无法将其分配给 readOnlyPoint .然后你会说“哦,对了,它是只读的,这是有原因的。我为什么要改变它?幸好编译器现在阻止了我。”

更新:关于你改写的请求......我想我知道你在说什么。在某种程度上,您可以“认为”结构在内部是不可变的,修改结构与用修改后的副本替换它是一样的。据我所知,它甚至可能是 CLR 在内存中内部执行的操作。 (这就是闪存的工作原理。你不能只编辑几个字节,你需要将一整块千字节读入内存,修改你想要的几个,然后写回整个块。)但是,即使它们是“内部不可变的” ",这是一个实现细节,对于我们作为结构用户的开发人员(他们的接口(interface)或 API,如果你愿意的话),它们是可以改变的。我们不能忽视这个事实并“认为它们是不可变的”。
在评论中,您说“您不能引用字段或变量的值”。您假设每个 struct 变量都有不同的副本,这样修改一个副本不会影响其他副本。这并不完全正确。以下标记的行不可替换,如果...
interface IFoo { DoStuff(); }
struct Foo : IFoo { /* ... */ }

IFoo otherFoo = new Foo();
IFoo foo = otherFoo;
foo.DoStuff(whatEverArgumentsYouLike); // line #1
foo = foo.DoStuff(whatEverArgumentsYouLike); // line #2
第 1 行和第 2 行没有相同的结果......为什么?因为 foootherFoo引用相同的 Foo 盒装实例。无论在 foo 中发生了什么变化第 1 行反射(reflect)在 otherFoo .第 2 行替换 foo使用新值并且对 otherFoo 没有任何作用(假设 DoStuff() 返回一个新的 IFoo 实例并且不修改 foo 本身)。
Foo foo1 = new Foo(); // creates first instance
Foo foo2 = foo1; // create a copy (2nd instance)
IFoo foo3 = foo2; // no copy here! foo2 and foo3 refer to same instance
修改 foo1不会影响foo2foo3 .修改 foo2将反射(reflect)在 foo3 ,但不在 foo1 .修改 foo3将反射(reflect)在 foo2但不在 foo1 .
令人困惑?坚持不可变的值类型,你就消除了修改它们中的任何一个的冲动。

更新:修复了第一个代码示例中的错字

关于c# - 根据定义,值类型是不可变的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/868411/

相关文章:

c# - 在 Visual Studio 中使用 --useHttps 运行 Azure Functions 项目时出错

c# - 在C#中播放音频时调整平衡

C# 性能 MS verse Mono 问题

javascript - 如何正确地传递 immutablejs 对象

javascript - 避免递归函数中的状态突变(Redux)

c# - IIS8 没有端点监听 net.tcp :

c# - WPF 4 中仍然存在的内存泄漏

JavaScript 安全 : force deletion of sensitive data

C# Facebook SDK 将 accesstoken 延长至 60 天

c# - 处理计时器的最佳方法?