7

当像这样序列化树结构时,我得到一个ProtoException("Possible recursion detected (offset: 4 level(s)): o EOW"):

var tree = new PrefixTree();
        tree.Add("racket".ToCharArray());
        tree.Add("rambo".ToCharArray());
        using (var stream = File.Open("test.prefix", FileMode.Create))
        {
            Serializer.Serialize(stream, tree);
        }

树实现:

[ProtoContract]
public class PrefixTree
{
    public PrefixTree()
    {
        _nodes = new Dictionary<char, PrefixTree>();
    }

    public PrefixTree(char[] chars, PrefixTree parent)
    {
        if (chars == null) throw new ArgumentNullException("chars");
        if (parent == null) throw new ArgumentNullException("parent");
        if (chars.Length == 0) throw new ArgumentException();

        _parent = parent;
        _nodes = new Dictionary<char, PrefixTree>();
        _value = chars[0];

        var overflow = chars.SubSet(1);
        if (!overflow.Any()) _endOfWord = true;
        else Add(overflow.ToArray());
    }

    [ProtoMember(1)]
    private readonly char _value;
    [ProtoMember(2)]
    private readonly bool _endOfWord;
    [ProtoMember(3)]
    private readonly IDictionary<char, PrefixTree> _nodes;
    [ProtoMember(4, AsReference = true)]
    private readonly PrefixTree _parent;

    public void Add(char[] word)
    {
        if (word == null) throw new ArgumentNullException("word");
        if (word.Length == 0) return;

        var character = word[0];
        PrefixTree node;
        if (_nodes.TryGetValue(character, out node))
        {
            node.Add(word.SubSet(1));
        }
        else
        {
            node = new PrefixTree(word, this);
            _nodes.Add(character, node);
        }
    }

    public override string ToString()
    {
        return _endOfWord ? _value + " EOW" : _value.ToString();
    }
}

public static class ListHelper
{
    public static char[] SubSet(this char[] source, int start)
    {
        return source.SubSet(start, source.Length - start);
    }

    public static char[] SubSet(this char[] source, int start, int length)
    {
        if (start < 0) throw new ArgumentOutOfRangeException();
        if (start > source.Length) throw new ArgumentOutOfRangeException();
        if (length < 0) throw new ArgumentOutOfRangeException();

        var result = new char[length];
        Array.Copy(source, start, result, 0, length);
        return result;
    }
}

我是用错误的属性装饰还是我只是设计了一个不可序列化的树?

编辑:试过这个无济于事:

var typeModel = RuntimeTypeModel.Default;
        var type = typeModel.Add(typeof(PrefixTree), false);
        type.AsReferenceDefault = true;
        type.Add("_value", "_endOfWord", "_nodes", "_parent");

        var tree = new PrefixTree();
        tree.Add("racket".ToCharArray());
        tree.Add("rambo".ToCharArray());
        using (var stream = File.Open("test.prefix", FileMode.Create))
        {
            typeModel.Serialize(stream, tree);
        }
4

1 回答 1

3

_nodes 的 _parent 和 Value 都指向相同的类型( PrefixTree ),但只有 _parent 被标记为“AsReference”。

如果您遍历序列化堆栈,您将看到 Dictionary 值的 Value 是独立于 _parent 项进行序列化的,并且不会检查重复实例。

当它遍历树时,内部序列化深度检查为 25,它开始检测重复实例。如果这个值更大,它不会抛出异常,如果它更小,它会抛出树上更高的节点。

我也不认为这将是可反序列化的,当然如果这样做,每个子节点的 _parent 字段的值将与 _nodes 容器的实例不同。

您需要创建自己的字典类型(子类 Dictionary<,> 或实现 IDictionary<,> ),以便您可以添加 [ProtoContract] 属性并控制字典项目的序列化。

IE

[ProtoContract]
public class NodeItem
{
    [ProtoMember(1)]
    public char Key { get; set; }
    [ProtoMember(2, AsReference = true)]
    public PrefixTree Value { get; set; }
}

[ProtoContract]
public class Nodes : IDictionary<char, PrefixTree>
{
    private readonly IDictionary<char, PrefixTree> inner;

    [ProtoMember(1)]
    public NodeItem[] Items
    {
        get
        {
            return this.inner.Select(item => new NodeItem() {Key = item.Key, Value = item.Value}).ToArray();
        }
        set
        {
            foreach( NodeItem item in value)
            {
                this.inner.Add(item.Key, item.Value);
            }
        }
    }
    ... // Omitted IDictionary members for clarity

这里的关键是获取附加到节点 PrefixTree 的 AsReference 元数据。另请注意,Items 正在返回一个数组,如果您希望它作为一个列表,那么您需要使用设置 OverwriteList 属性成员。

我还需要删除 PrefixTree 类型中每个字段的只读关键字。这个单元测试通过了我。

        [TestMethod]
    public void TestMethod1()
    {
        var tree = new PrefixTree();
        tree.Add("racket".ToCharArray());
        tree.Add("rambo".ToCharArray());

        PrefixTree tree2 = null;

        using (var stream = new MemoryStream())
        {
            Serializer.Serialize(stream, tree);
            stream.Position = 0;
            tree2 = Serializer.Deserialize<PrefixTree>(stream);
        }


        Assert.IsNotNull(tree2);
        Assert.AreEqual(tree._nodes.Count, tree2._nodes.Count);
        Assert.AreEqual(2, tree2._nodes['r']._nodes['a']._nodes.Count);      // 'c' and 'm'
        Assert.AreEqual('c', tree2._nodes['r']._nodes['a']._nodes.Values.First().Value);
        Assert.AreEqual('m', tree2._nodes['r']._nodes['a']._nodes.Values.Last().Value);
    }
于 2012-09-28T02:33:49.653 回答