0

我想了解如何在不使用框架 XmlSerializer 的情况下实现序列化/反序列化。序列化应该支持图表。我遇到了protobuf-net,它看起来可以解决问题,但这是一个非常复杂的项目,我有点迷失了方向。我还找到了 NetSerializer,但它看起来不支持图表。有没有更简单的项目?

4

1 回答 1

0

DataContractSerializer,类似:

[DataContract(IsReference = true)]
class Foo
{
    [DataMember(Order = 1)]
    public Foo Parent { get; set; }
}

应该管用。对于 protobuf-net,非常相似的东西应该可以工作:

[ProtoContract(AsReferenceDefault = true)]
class Foo
{
    [ProtoMember(1)]
    public Foo Parent { get; set; }
}

来自评论:

我的问题不是如何使用它。这就是它是如何从后面实现的

要从头开始实现,这基本上涉及的是:当您第一次看到一个对象时(通常使用引用相等,而不是Equals/GetHashCode覆盖或==重载),您发明了一个新的唯一标识符。这个标识符可以是一个Guid,一个随机数,或者更简单的...,,,,...1所以当你第一次看到一个对象时,你会说“这是一个新对象,身份,内容{...}” ; 然后每当您再次看到它,您只需说“参考”。2311

如果您的目标是核心 .NET,则可以使用ObjectIDGenerator

using System.Runtime.Serialization;
static class Program
{
    static void Main()
    {
        var idgen = new ObjectIDGenerator();
        object foo = new object(), bar = new object(), blap = foo;
        Write(idgen, foo);
        Write(idgen, bar);
        Write(idgen, blap); // note this is the same object as foo
    }
    static void Write(ObjectIDGenerator idgen, object obj)
    {
        bool isNew;
        long id = idgen.GetId(obj, out isNew);
        System.Console.WriteLine("{0}, {1}", id, isNew);
    }
}

输出:

1, True
2, True
1, False

但是因为 protobuf-net 的目标是一系列平台,而这些平台并不总是可用的,所以它在内部实现了这一点。作为实现细节,对于 protobuf-net,这是NetObjectCache.AddObjectKey方法(可能应该被称为GetObjectKey,考虑一下)。实现的确切方式取决于目标框架,但首选实现只是Dictionary<object,int>使用自定义比较器来确保不使用多态GetHashCode()/ :Equals

private sealed class ReferenceComparer : System.Collections.Generic.IEqualityComparer<object>
{
    public readonly static ReferenceComparer Default = new ReferenceComparer();
    private ReferenceComparer() {}

    bool System.Collections.Generic.IEqualityComparer<object>.Equals(object x, object y)
    {
        return x == y; // ref equality
    }

    int System.Collections.Generic.IEqualityComparer<object>.GetHashCode(object obj)
    {
        return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
    }
}

那么它只是TryGetValue/的一个例子Add,即

if (!objectKeys.TryGetValue(value, out index)) index = -1;
...
if (!(existing = index >= 0))
{
    index = list.Add(value); // <=== creates a new identity
    ...
    objectKeys.Add(value, index); // <=== adds to the dictionary
}

请注意,它会稍微复杂一些,因为 protobuf-net 以不同的方式处理字符串 - 不是作为引用,而是作为strings - 以确保如果两个字符串的值相同,但是是不同的字符串引用,它使用相同的引用并且只存储字符串一次。相比之下,BinaryFormatter/ObjectIdGenerator在这里做出了另一个选择,以保留重复字符串的引用标识。您可以使用以下代码在前面的代码中看到这一点:

Write(idgen, new string('a', 3));
Write(idgen, new string('a', 3));

有关信息,编写身份+有效负载与参考的详细信息在 中BclHelpers.WriteNetObject,但请注意,这是特定于实现/格式的。但基本上它使用不同的数字键来指示“这是一个新对象/引用”以“重新使用现有对象”,通过:

bool existing;
int objectKey = dest.NetCache.AddObjectKey(value, out existing);
ProtoWriter.WriteFieldHeader(existing
    ? FieldExistingObjectKey : FieldNewObjectKey, WireType.Variant, dest);
ProtoWriter.WriteInt32(objectKey, dest);
if (existing)
{
    writeObject = false;
}

并且只为新对象写入对象有效负载

于 2013-09-19T07:24:50.973 回答