2012-05-18 10 views
7

recibo una ProtoException ("recursividad Posible detectado (offset: 4 niveles (s)): o ODM") al serializar una estructura de árbol, así:Serialize árbol prefijo

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

La implementación del árbol:

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

¿Estoy decorando con los atributos incorrectos o simplemente he diseñado un árbol no serializable?

Editar: trató este vano:

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

¿Cuál es su método de extensión 'SubSet'? Necesito entender eso para obtener una repro de trabajo. También; ¿Cuál es el método de 'Agregar'? –

+0

Sin embargo! El principal problema aquí es que el manejador de "diccionario" no usa tipos de referencia de forma predeterminada. Podría ver más si puedo obtener una reprografía funcional. –

+0

Re la edición, todavía tienen "Error Sin sobrecarga para el método 'SubSet' toma 2 argumentos" - en el método ListHelper.SubSet –

Respuesta

3

El _parent y valor de los _nodes ambos apuntan al mismo tipo (PrefixTree), pero sólo el _parent está marcado como "AsReference".

Si recorre la pila de serialización, verá que el valor del valor del diccionario se serializa independientemente del elemento _parent y no se marca para una instancia duplicada.

Mientras camina por el árbol hay una verificación de profundidad de serialización interna de 25, en la que comienza a detectar las instancias duplicadas. Si este valor fuera más grande, no arrojaría una excepción; si fuera más pequeño, arrojaría un nodo más arriba en el árbol.

Tampoco creo que esto pueda ser deserializable, y ciertamente si lo hiciera, el valor del campo _parent de cada nodo hijo no será la misma instancia que el contenedor _nodes.

Necesita crear su propio tipo de diccionario (subclase Dictionary <,> o implementar IDictionary <,>) para que pueda agregar el atributo [ProtoContract] y controlar la serialización de los elementos del diccionario.

es decir

[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 

la clave aquí es conseguir que los metadatos AsReference unido a PrefixTree de los nodos. También tenga en cuenta que Items está devolviendo una matriz, si la quiere como una lista, entonces necesita usar establecer el miembro de atributo OverwriteList.

También necesitaba eliminar la palabra clave readonly para cada campo en el tipo PrefixTree. Esta prueba de unidad pasó para mí.

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