2012-05-18 13 views
7

dostaję ProtoException ("Możliwe rekursji wykryty (offset: 4 Poziom (y)): o EOW") podczas szeregowania strukturę drzewa tak:Serializować prefiks drzewo

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); 
     } 

Realizacja drzewo:

[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; 
    } 
} 

Czy ozdabiam z niewłaściwymi atrybutami, czy po prostu zaprojektowałam niezdatne do sfotografowania drzewo?

Edit: Próbowałem to bezskutecznie:

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); 
     } 
+0

Jaka jest twoja metoda rozszerzenia SubSet? trzeba to zrozumieć, aby uzyskać działającą repro. Również; jaka jest metoda "Dodaj"? –

+0

Jednak! Głównym problemem jest to, że obsługa "słownika" domyślnie nie używa typów referencyjnych. Mogę być w stanie wyglądać bardziej, jeśli uda mi się uzyskać działającą repro. –

+0

Po edycji, nadal występuje błąd "Błąd Bez przeciążenia dla metody" SubSet "przyjmuje 2 argumenty" - w metodzie ListHelper.SubSet –

Odpowiedz

3

_parent oraz wartość _nodes zarówno punkt do tego samego typu (PrefixTree), ale tylko _parent jest oznaczony jako "AsReference".

Po przejściu stosu do serializacji zobaczysz, że wartość Słownika jest serializowana niezależnie od elementu _parent i nie jest zaznaczona dla duplikatu.

Po przejściu przez drzewo następuje wewnętrzna kontrola głębokości serializacji 25, przy której rozpoczyna się wykrywanie duplikatów. Jeśli ta wartość byłaby większa, nie wyrzuciłaby wyjątku, gdyby była mniejsza, rzuciłaby na węzeł znajdujący się wyżej w drzewie.

Nie sądzę również, że byłoby to możliwe do deserializacji, a na pewno gdyby tak się stało, wartość pola podstawowego dla każdego węzła potomnego nie będzie taka sama jak kontener _nodes.

Musisz utworzyć jesteś własny rodzaj słownika (podklasa słownik <,> lub wdrożyć IDictionary <,>), więc można dodał [ProtoContract] atrybut i kontrolować serializacji elementów w słowniku.

tj

[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 

kluczem tutaj jest uzyskać metadanych AsReference dołączony do PrefixTree węzłów męska. Zwróć też uwagę, że Elementy zwracają tablicę, jeśli chcesz, aby była to lista, musisz użyć ustawienia członka atrybutu OverwriteList.

Potrzebowałem również usunąć słowo kluczowe readonly dla każdego pola w rodzaju PrefixTree. Ten test jednostkowy przeszedł dla mnie.

 [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); 
    }