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
。如果 Parent
是 null
,arr
将被分配为未修改的,否则 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
可以看出,经过偶数次赋值后arr
的Parent
设置为null
。
在设置第一个赋值 arr.Parent
之后(JProperty
包含 Name
及其属于 JOject
的 Value
。在第二次赋值期间,由于 arr
已经有一个 Parent
,arr
将被克隆并且克隆的值将被设置为 obj["arr"]
。由于设置了新的 JToken
,先前 JToken
的 Parent
将设置为 null
即 arr.Parent
将变为 null
.
同样在第三次作业中,arr.Parent
是 null
。所以它会像第一次分配一样设置。
对于场景 2 arr
已经有一个 Parent
,因此在第二次分配期间,即 obj2["arr"] = arr;
,arr
将被克隆和设置。
我有 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
。如果 Parent
是 null
,arr
将被分配为未修改的,否则 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
可以看出,经过偶数次赋值后arr
的Parent
设置为null
。
在设置第一个赋值 arr.Parent
之后(JProperty
包含 Name
及其属于 JOject
的 Value
。在第二次赋值期间,由于 arr
已经有一个 Parent
,arr
将被克隆并且克隆的值将被设置为 obj["arr"]
。由于设置了新的 JToken
,先前 JToken
的 Parent
将设置为 null
即 arr.Parent
将变为 null
.
同样在第三次作业中,arr.Parent
是 null
。所以它会像第一次分配一样设置。
对于场景 2 arr
已经有一个 Parent
,因此在第二次分配期间,即 obj2["arr"] = arr;
,arr
将被克隆和设置。