【问题标题】:How to find out if an object is referencing another object?如何判断一个对象是否引用了另一个对象?
【发布时间】:2024-01-03 11:33:01
【问题描述】:

我无法从模板字典创建类实例的副本。似乎MemberwiseClone() 留下了一些引用字典模板字段的字段。我希望能够以一种方便的方式查看这是否如此,就像 Visual Studio 的 DataTips 提供的那样。

有没有办法查明引用类型对象(或其字段)的实例是否正在引用相同类型的另一个实例(在成员克隆之后)?

【问题讨论】:

    标签: c# .net clone reference-type


    【解决方案1】:

    规则是任何值类型都会被复制,任何引用类型只会复制引用。这是一个浅拷贝。

    如果这不是您想要的行为,那么您需要推出自己的克隆方法。

    您可能在谈论深层副本,在这种情况下,这将告诉您您需要了解的内容:How do you do a deep copy of an object in .NET (C# specifically)?

    至于计算对实例的引用数,Eric Lippert 说 C# 不做引用计数 C# - Get number of references to object 所以你必须自己动手。但我不认为这是你想要做的。

    【讨论】:

    • 我只是想确保副本的任何字段都没有引用其他字段(否则会产生不需要的行为)。但是如果有一个工具可以在我每次不小心制作浅拷贝时发出警告,这可能会很方便。
    【解决方案2】:

    您可以使用内存分析器手动检查引用。见.NET Memory Profiling Tools

    【讨论】:

    • 我宁愿不使用消耗所有资源的分析器,而是使用反射之类的东西,这样我就可以在屏幕上打印出符合条件的字段。
    【解决方案3】:

    Java 的一个“特性”是实际上只有一种非原始类型:对象引用,它可以以各种方式使用。虽然这使框架易于实现,但这意味着变量的类型不足以描述其含义。虽然 .net 在许多方面都对 Java 进行了改进,但它也存在这个根本性的弱点。

    例如,假设对象George 具有IList<String> 类型的字段Bob [或者,对于Java,list<string>]。这样一个字段至少可以代表五种根本不同的事物:

    1. 它持有一个对象的引用,该对象持有一组字符串,它永远不允许对其进行任何更改。如果该列表的第 5 项是“Barney”,那么该列表中的第 5 项将永远不会是“Barney”以外的任何其他内容。在这种情况下,`Bob` 只封装了列表的不可变方面。此外,可以免费将引用共享给对 George 状态的该方面感兴趣的任何代码。
    2. 它持有一个对象的引用,该对象持有一组字符串,任何持有该引用的人都可以修改该字符串,但任何拥有该对象引用的实体都不会修改列表,也不允许它暴露于任何可能这样做的对象.更糟糕的是,虽然列表允许更改其内容,但实际上没有任何内容会改变这些内容。如上所述,`Bob` 仅封装了列表的不可变方面,但`George` 负责通过仅将引用公开给可以信任不修改列表的代码来维护这种不变性。
    3. 它拥有唯一的引用,在宇宙的任何地方,一个对象持有一组字符串,它可以随意修改。在这种情况下,`Bob` 封装了列表的*可变状态*。如果复制“George”,则必须创建一个新列表,其中包含与旧项目相同的项目,并为该副本提供参考。另请注意,`George` 不能将引用传递给任何可能保留引用的代码,无论该代码是否会尝试修改列表。
    4. 它包含对某个列表的引用,该列表由某个其他对象“拥有”,该列表将用于将项目添加到列表中以使其他对象受益,或者观察其他对象放入列表中的内容`乔治`的好处。在这种情况下,`Bob` 封装了列表的 *identity*。在正确的克隆中,`Bob` 必须识别 *same list* 与原始列表中的一样。
    5. 它持有对它所拥有的列表的引用,该列表将被改变,但其他一些对象也持有该列表的引用(也许他们可以为了 `George` 的利益将东西添加到列表中,或者可能这样他们就可以看到 `George` 对列表所做的事情)。在这种情况下,`Bob` 封装了*可变状态和身份*。包含这两个方面的字段的存在意味着*如果没有其他对象的合作,就不可能制作语义正确的“George”副本*。

    简而言之,Bob 可以封装列表的可变状态、它的标识,或者两者都封装,或者两者都不封装(除了标识之外的不可变状态是“免费赠品”)。如果它只封装可变状态,George 的语义正确副本必须让Bob 引用一个不同的列表,该列表使用相同的内容进行初始化。如果它仅封装身份,则语义正确的副本必须让Bob 引用相同的列表。如果它同时封装了可变状态和不可变状态,George 就无法单独正确克隆。两者都不做的字段可以复制也可以不复制,方便的话。

    如果可以正确确定哪些字段封装了引用对象的可变状态,哪些封装了身份,哪些两者兼而有之,那么语义上正确的克隆操作应该做什么就很明显了。不幸的是,框架中没有以这种方式对字段进行分类的标准约定,因此您必须想出自己的方法,然后使用它的克隆方案。

    【讨论】: