为啥 XElement 的行为不像引用类型?

Posted

技术标签:

【中文标题】为啥 XElement 的行为不像引用类型?【英文标题】:Why does XElement not behave like a reference type?为什么 XElement 的行为不像引用类型? 【发布时间】:2021-08-18 15:30:24 【问题描述】:

我注意到XElement 是一个类,所以我尝试了类似的方法:

var doc = new XDocument(
    new XDeclaration("1.0", "utf8", "yes"),
    new XElement("Root")
    );
var root = doc.Root;
var com = new XElement("Component", new XAttribute("name", "arm"));
root.Add(com);
root.Add(com);
root.Add(com);
com.Add(new XAttribute("type", 1));

Console.WriteLine(doc);

但输出是:

<Root>
  <Component name="arm" type="1" />
  <Component name="arm" />
  <Component name="arm" />
</Root>

我也试过SetAttributeValue(),得到了同样的结果。

为什么type属性只附加在第一个组件上?

【问题讨论】:

因为它是made that way。 “当添加 XNodeXAttribute 对象时,如果新内容没有父对象,则对象只是附加到 XML 树。如果新内容已经是父对象并且是另一个 XML 树的一部分,那么新内容被克隆,并且新克隆的内容被附加到 XML 树中。”如果您考虑一下,在询问文档顺序时,尝试维护引用语义会导致非常不直观的结果。 为什么不把com.Add(new XAttribute("type", 1));放在root.Add(com);前面,而不是后面? 如果您更喜欢查看sources,那么您会看到它会调用CloneNode() 方法来进行后续调用(因为将设置com 的父级)。通过修改com 实例,您只会更改未克隆的第一个节点。 @JeroenMostert 除非有 dup-target(我找不到),否则您可能希望将此作为答案发布。 【参考方案1】:

我在下面给出了第二个答案 - 更好,因为它给出了具体的设计原因 - 但留下了这个答案,因为它证明内部副本已经发生。


作为一个类并不能提供关于该类的行为或应该如何对待的线索。

使用调试器

每个节点都有不同的哈希值,因此必须假定为不同的对象(因此推测Add() 期间正在进行复制)。

如果你可以改变你的操作顺序,这就解决了问题

static void X()

  var doc = new XDocument(
    new XDeclaration("1.0", "utf8", "yes"),
    new XElement("Root")
  );
  var root = doc.Root;
  var com = new XElement("Component", new XAttribute("name", "arm"));
  com.Add(new XAttribute("type", 1));
  root.Add(com);
  root.Add(com);
  root.Add(com);

  Console.WriteLine(doc);

给予

<Root>
  <Component name="arm" type="1" />
  <Component name="arm" type="1" />
  <Component name="arm" type="1" />
</Root>

【讨论】:

它没有解决问题,它隐藏了它。所有节点上存在的属性可能会让您认为它们确实是同一个实例,但事实并非如此。它们和以前一样是三个副本。 @GSerg:我同意它不直观,但这并没有错。显然,这是设计行为。我建议更改类的使用以修复行为,您是否建议更改类以适应这种类型的使用?【参考方案2】:

我最初的答案是(本质上是“设计”),这就是为什么......

来自MS Documentation(并点击相关链接)您会发现

XElement 继承 XContainer 继承 XNode XContainer 具有方法 Add() 和属性 FirstNodeLastNode XNode 具有属性 NextNodePreviousNode

如果Add() 盲目地添加对同一对象的引用,而没有在必要时创建副本以避免多重引用,那么如何避免循环引用?在上面的示例中,FirstNodeFirstNode.NextNode 将引用同一个对象。

【讨论】:

以上是关于为啥 XElement 的行为不像引用类型?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 g++ 接受缺少基础类型的引用类型?

为啥我必须使用 .self 来快速引用类型对象?

为啥泛型类型只能引用类型,而不能是基本类型

为啥在向量类中实现 operator= 时返回 const 引用

为啥在使用静态方法时取消引用 nullptr 而不是 C++ 中的未定义行为?

Java:父类对象为啥能转换成子类对象