我正在使用类型对象模式的变体(基本上是智能枚举)。由于这个问题最好用代码解释,我会直接进入它。
class Program
{
static void Main(string[] args)
{
Test C = Test.B;
Console.WriteLine(C == Test.B); //Returns true
string Json = JsonConvert.SerializeObject(C);
C = JsonConvert.DeserializeObject<Test>(Json);
Console.WriteLine(C == Test.B); //Returns false
}
}
public class Test
{
public int A { get; set; }
public Test(int A)
{
this.A = A;
}
public static Test B = new Test(100);
}
在此示例中,Test是类型对象,并且它的实例被分配给它的静态字段B.在现实生活场景中,将存在多个这些静态字段,每个字段表示不同的类型。当我序列化和反序列化时,测试对象纯粹作为数据被序列化。我理解为什么会这样,但我不知道该怎么办。我想以某种方式保留Test的实例,引用该类中的静态成员。
答案 0 :(得分:1)
您正在寻找的是对IObjectReference
界面的支持:
在对不同对象的引用的对象上实现此接口,在完全还原当前对象之前无法解析该对象。在修复阶段,任何实现IObjectReference的对象都会查询其真实对象,并将该对象插入到图形中。
不幸的是,Json.NET不支持此接口。但是,在有问题的类型也实现ISerializable
的情况下,扩展Json.NET以支持此接口非常容易。这是一个非常合理的限制,因为在实践中,这两个接口通常一起使用,如documentation example中所示。
首先,介绍以下custom contract resolver:
public class ISerializableRealObjectContractResolver : DefaultContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
static ISerializableRealObjectContractResolver instance;
static ISerializableRealObjectContractResolver() { instance = new ISerializableRealObjectContractResolver(); }
public static ISerializableRealObjectContractResolver Instance { get { return instance; } }
public ISerializableRealObjectContractResolver()
: base()
{
this.IgnoreSerializableInterface = false;
}
protected override JsonISerializableContract CreateISerializableContract(Type objectType)
{
var contract = base.CreateISerializableContract(objectType);
var constructor = contract.ISerializableCreator;
contract.ISerializableCreator = args =>
{
var obj = constructor(args);
if (obj is IObjectReference)
{
var context = (StreamingContext)args[1];
obj = ((IObjectReference)obj).GetRealObject(context);
}
return obj;
};
return contract;
}
}
现在,修改您的psuedo-enum Test
类型以实施ISerializable
和IObjectReference
:
public class Test : ISerializable, IObjectReference
{
readonly int a;
public int A { get { return a; } }
public Test(int A)
{
this.a = A;
}
public static readonly Test B = new Test(100);
#region ISerializable Members
protected Test(SerializationInfo info, StreamingContext context)
{
a = info.GetInt32("A");
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("A", A);
}
#endregion
#region IObjectReference Members
public object GetRealObject(StreamingContext context)
{
// Check all static properties to see whether the key value "A" matches. If so, return the static instance.
if (this.A == B.A)
return B;
return this;
}
#endregion
}
我也将类型设为不可变,因为这显然是这里的要求。
现在,在使用此合约解析程序时,您的单元测试将通过:
Test C = Test.B;
Console.WriteLine(C == Test.B); //Returns true
string Json = JsonConvert.SerializeObject(C, new JsonSerializerSettings { ContractResolver = ISerializableRealObjectContractResolver.Instance });
Console.WriteLine(Json);
C = JsonConvert.DeserializeObject<Test>(Json, new JsonSerializerSettings { ContractResolver = ISerializableRealObjectContractResolver.Instance });
Console.WriteLine(C == Test.B); //Still returns true
if (!object.ReferenceEquals(C, Test.B))
{
throw new InvalidOperationException("!object.ReferenceEquals(C, Test.B)");
}
else
{
Console.WriteLine("Global singleton instance deserialized successfully.");
}
但请注意,Json.NET仅支持完全信任的ISerializable
接口。
答案 1 :(得分:0)
默认情况下不可行,因为JSON反序列化器不关心类中的现有引用或静态对象。
您可以使用自定义Equals
方法比较相等性,但我想这并不是您想要的。
答案 2 :(得分:0)
不要序列化MyObj.Test,使用Ignore属性来抑制它。而是公开一个返回MyObj.Test.ID的属性MyObj.TestID。在MyObj上设置TestID时,从ID键控的静态集合加载Test,并将MyObj.Test设置为该值。
答案 3 :(得分:0)
首先,当您不希望每次定义基类的新派生时都不想通过继承层次结构时,应该使用Type Object
模式。将类型对象附加为static
并不能让人感到自豪。正如你所提到的那样,我不会跳过它。
看起来你希望能够在使用json.net进行反序列化后保留引用。
现在,如果您想这样做,可能需要查看here。
从上述链接中获取片段,因为这里有一个示例,因为这是StackOverflow的答案。它甚至可以维持所提供的链接已经死亡。
您的第一个选择是使用默认PreserveReferencesHandling
。关联的示例位于您可以引用列表中的相同对象并指向它的位置。我不认为它实际上保留了旧参考,但当您在列表中有相同的内容并且您不想使用自己的IEqualityComparer
或IEquatable
实现时,确实会有所帮助:
string json = JsonConvert.SerializeObject(people, Formatting.Indented,
new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
//[
// {
// "$id": "1",
// "Name": "James",
// "BirthDate": "1983-03-08T00:00Z",
// "LastModified": "2012-03-21T05:40Z"
// },
// {
// "$ref": "1"
// }
//]
List<Person> deserializedPeople = JsonConvert.DeserializeObject<List<Person>>(json,
new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
Console.WriteLine(deserializedPeople.Count);
// 2
Person p1 = deserializedPeople[0];
Person p2 = deserializedPeople[1];
Console.WriteLine(p1.Name);
// James
Console.WriteLine(p2.Name);
// James
bool equal = Object.ReferenceEquals(p1, p2);
// true
您可以使用IsReference
属性来控制将哪些属性保留为引用:
[JsonObject(IsReference = true)]
public class EmployeeReference
{
public string Name { get; set; }
public EmployeeReference Manager { get; set; }
}
现在,如果你想在代码中为自己保留完全相同的引用(我认为这不是一个好的设计,你可能只需要Equality
比较方法并完成它),您需要自定义IReferenceResolver
定义here。
此外,如果您想拥有类似的东西,请查看Json.net的源代码here。
您可以使用IdReferenceResolver
将对象引用保存为Guid,并可能按照您的方式使用它。
如果您想了解DefaultReferenceResolver
的工作原理,可以查看此stackoverflow thread。