gpt4 book ai didi

c# - 相互引用的不可变对象(immutable对象)?

转载 作者:IT王子 更新时间:2023-10-29 03:40:16 25 4
gpt4 key购买 nike

今天,我试图围绕相互引用的不可变对象(immutable对象)进行思考。我得出的结论是,如果不使用惰性求值,你不可能做到这一点,但在这个过程中我写了这个(在我看来)有趣的代码。

public class A
{
public string Name { get; private set; }
public B B { get; private set; }
public A()
{
B = new B(this);
Name = "test";
}
}

public class B
{
public A A { get; private set; }
public B(A a)
{
//a.Name is null
A = a;
}
}

我觉得有趣的是,我想不出另一种方法来观察处于尚未完全构造且包括线程的状态的类型 A 的对象。为什么这甚至有效?有没有其他方法可以观察未完全构造的对象的状态?

最佳答案

Why is this even valid?


为什么你认为它是无效的?

Because a constructor is supposed to guarantee that the code it contains is executed before outside code can observe the state of the object.


正确。但是编译器不负责维护该不变量。你是。如果您编写的代码破坏了这种不变性,并且这样做会很痛苦,那么请停止这样做。

Are there any other ways to observe the state of an object that is not fully constructed?


当然。对于引用类型,所有这些都涉及以某种方式将“this”传递出构造函数,显然,因为唯一保存对存储的引用的用户代码是构造函数。构造函数泄漏“this”的一些方法是:
  • 将“this”放在静态字段中并从另一个线程引用它
  • 进行方法调用或构造函数调用并将“this”作为参数传递
  • 进行虚拟调用 - 如果虚拟方法被派生类覆盖,则尤其令人讨厌,因为它会在派生类 ctor 主体运行之前运行。

  • 我说只有 用户代码 持有引用的是ctor,但当然 垃圾收集器 也有引用。因此,可以观察到对象处于半构造状态的另一种有趣方式是,如果对象具有析构函数,并且构造函数抛出异常(或获取异步异常,如线程中止;稍后会详细介绍。 ) 在这种情况下,对象即将死亡,因此需要终结,但终结器线程可以看到对象的半初始化状态。现在我们又回到了可以看到半构造对象的用户代码中!
    面对这种情况,析构函数必须是健壮的。 析构函数不能依赖于被维护的构造函数设置的对象的任何不变量,因为被销毁的对象可能永远不会被完全构造。
    另一个可以被外部代码观察到的半构造对象的疯狂方式当然是如果析构函数在上面的场景中看到半初始化的对象,然后将对该对象的引用复制到静态字段,从而确保- 构建的、半成品的物体从死亡中被拯救出来。 请不要那样做。 就像我说的,如果它很痛,就不要这样做。
    如果您在值类型的构造函数中,那么事情基本相同,但在机制上存在一些细微差别。该语言要求对值类型的构造函数调用创建一个只有构造函数可以访问的临时变量,对该变量进行变异,然后将变异值的结构副本复制到实际存储中。这确保了如果构造函数抛出异常,则最终存储不会处于半变异状态。
    请注意,由于结构副本不能保证是原子的,因此另一个线程可能会看到处于半变异状态的存储;如果您处于这种情况,请正确使用锁。此外,像线程中止这样的异步异常可能会在结构体副本中途抛出。无论副本是来自 ctor 临时副本还是“常规”副本,都会出现这些非原子性问题。一般来说,如果有异步异常,很少有不变量被维护。
    在实践中,如果 C# 编译器可以确定不会出现这种情况,它就会优化掉临时分配和复制。例如,如果新值正在初始化一个未被 lambda 关闭且不在迭代器块中的局部变量,则 S s = new S(123);只是变异 s直接。
    有关值类型构造函数如何工作的更多信息,请参阅:
    Debunking another myth about value types
    有关 C# 语言语义如何试图让您远离自己的更多信息,请参阅:
    Why Do Initializers Run In The Opposite Order As Constructors? Part One
    Why Do Initializers Run In The Opposite Order As Constructors? Part Two
    我似乎偏离了手头的话题。在结构体中,您当然可以以相同的方式观察要半构造的对象——将半构造的对象复制到静态字段,以“this”作为参数调用方法,等等。 (显然,在派生程度更高的类型上调用虚拟方法对于结构来说不是问题。)而且,正如我所说,从临时存储到最终存储的复制不是原子的,因此另一个线程可以观察到半复制的结构。

    现在让我们考虑问题的根本原因:如何制作相互引用的不可变对象(immutable对象)?
    通常,正如您发现的那样,您不会。如果您有两个相互引用的不可变对象(immutable对象),那么在逻辑上它们形成一个有向循环图。您可能会考虑简单地构建一个不可变的有向图!这样做很容易。一个不可变的有向图包括:
  • 不可变节点的不可变列表,每个节点都包含一个值。
  • 不可变节点对的不可变列表,每个节点对都具有图边的起点和终点。

  • 现在让节点 A 和 B 相互“引用”的方法是:
    A = new Node("A");
    B = new Node("B");
    G = Graph.Empty.AddNode(A).AddNode(B).AddEdge(A, B).AddEdge(B, A);
    大功告成,你有一个图表,其中 A 和 B 相互“引用”。
    当然,问题是如果没有 G 就无法从 A 到达 B。拥有这种额外的间接级别可能是 Not Acceptable 。

    关于c# - 相互引用的不可变对象(immutable对象)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7661538/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com