JObject 正在丢失引用

JObject is losing reference

我有 2 个场景来说明这个问题。

场景 1

using System;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var arr = new JArray();
        arr.Add("apple");
        var obj = new JObject();
        obj["arr"] = arr;
        obj["arr"] = arr;       
        arr.Add("mango");
        
        foreach(var a in obj["arr"]){
            Console.WriteLine(a);
        }
        
                            
    }
}

这里obj["array"]应该是引用了arr,也就是早点初始化。所以输出应该是

apple
mango

但输出是

apple

场景 2

using System;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var arr = new JArray();
        arr.Add("apple");
        var obj = new JObject();
        obj["arr"] = arr;
        
        var obj2 = new JObject();
        obj2["arr"] = arr;      
        
        arr.Add("mango");
        
        foreach(var a in obj2["arr"]){
            Console.WriteLine(a);
        }
    }
}

同样 obj2["arr"] 应该引用 arr。但事实并非如此。 所以预期的输出是

apple
mango

但输出是

apple

我对csharp不是很精通。如果我在这里遗漏了什么,请告诉我。

编辑

添加@Wyck 在评论中提到的另一个场景。

场景 3

using System;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var arr = new JArray();
        Console.WriteLine(arr.GetHashCode());
        arr.Add("apple");
        var obj = new JObject();
        obj["arr"] = arr;
        Console.WriteLine(obj["arr"].GetHashCode());
        obj["arr"] = arr;   
        Console.WriteLine(obj["arr"].GetHashCode());
        obj["arr"] = arr;   
        Console.WriteLine(obj["arr"].GetHashCode());
        
        arr.Add("mango");
        
        foreach(var a in obj["arr"]){
            Console.WriteLine(a);
        }
        
                            
    }
}

重复赋值 obj["arr"] = arr 奇数次返回 arr 的原始引用,但重复偶数次则不会。

这个输出将是

10465620
10465620
1190878
10465620
apple
mango

看到偶数分配的哈希码已更改。对于奇数分配,它又变成了以前的样子。

如果你查看 Newtonsoft.Json 的 source code,你会发现在将数组分配给 属性 时,它会创建它的副本:

public JProperty(string name, object? content)
{
    ...
    Value = IsMultiContent(content)
        ? new JArray(content)
        : CreateFromContent(content);
}

JObject的相关部分是here

您可以通过获取 obj2["arr"]arr(均为 JArray 类型)的散列码 (.GetHashCode()) 轻松地在您的代码中进行测试并观察他们会有所不同。

因此,为了能够添加到数组中,您需要在分配 属性 后通过 JObject 的实例访问它,或者您可以 re-assign数组添加到 属性 每当你添加一个元素。

此答案完全基于@dbc 的评论。这种令人惊讶的行为是由于 Json.net 的实现方式所致。原答案是.

所有JToken需要有一个Parent属性并且只能有一个Parent.

obj["arr"] = arr;

这里在设置arr之前,验证arr是否已经有Parent。如果 Parentnullarr 将被分配为未修改的,否则 arr 将被克隆,克隆的 arr 将被分配。这就是场景 1 和场景 2 行为背后的原因。

using System;
using Newtonsoft.Json.Linq;

public class Program
{
    public static void Main()
    {
        var arr = new JArray();
        
        arr.Add("apple");
        var obj = new JObject();
        obj["arr"] = arr;
        Console.WriteLine(arr.Parent == null ? "null" : arr.Parent.GetHashCode().ToString());
        obj["arr"] = arr;
        Console.WriteLine(arr.Parent == null ? "null" : arr.Parent.GetHashCode().ToString());
        obj["arr"] = arr;   
        Console.WriteLine(arr.Parent == null ? "null" : arr.Parent.GetHashCode().ToString());
        
        arr.Add("mango");
        
        foreach(var a in obj["arr"]){
            Console.WriteLine(a);
        }
        
                            
    }
}

此代码块的输出将是

10566368
null
10566368
apple
mango

可以看出,经过偶数次赋值后arrParent设置为null

在设置第一个赋值 arr.Parent 之后(JProperty 包含 Name 及其属于 JOjectValue。在第二次赋值期间,由于 arr 已经有一个 Parentarr 将被克隆并且克隆的值将被设置为 obj["arr"]。由于设置了新的 JToken,先前 JTokenParent 将设置为 nullarr.Parent 将变为 null.

同样在第三次作业中,arr.Parentnull。所以它会像第一次分配一样设置。

对于场景 2 arr 已经有一个 Parent,因此在第二次分配期间,即 obj2["arr"] = arr;arr 将被克隆和设置。